diff --git a/CHANGELOG.md b/CHANGELOG.md index 738a0d9f..54125917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ +## 0.42.4 +### 2025-02-06 +* NEW: `FirstLoss` as new endpoint, which will stress on `Default` assumption till 0.01 loss on input tranche. +* NEW: New prepayment /default assumption via `byTerm`, which vector curves are being applied via term of the assets. + + +## 0.42.3 +### 2025-02-04 +* NEW: `Multi-thread` on pool cashflow projection +* NEW: Expose `convexity` on bond/asset +* NEW: Add new prepayment assumption `PSA` for Monthly mortgage +* NEW: Add new prepayment/default vector assumption based on asset origin term + +## 0.42.1 +### 2025-02-02 +* NEW: add custom fee flow by `BondPaidPeriod` `PoolCollectedPeriod` index + + +## 0.42.0 +### 2025-02-01 +* ENHANCE: refactor `calcPmt` to boost 15x performance for mortgage cashflow projection. +* NEW: add `ScheduleByIndex` for bonds +* FIX: `fundWith` shall increase the bond balance +* ENHANCE: refactor Z-spread calc logic with numeric.root.finder + + ## 0.41.1 ### 2025-01-11 * NEW: `Multi Interest Bond` which used to model in bond with `step up` feature ( sub ordinated interest) in European diff --git a/Hastructure.cabal b/Hastructure.cabal index 5ffdf4b0..4d13d1f0 100644 --- a/Hastructure.cabal +++ b/Hastructure.cabal @@ -1,18 +1,18 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.37.0. +-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack name: Hastructure -version: 0.41.1 +version: 0.41.3 description: Please see the README on GitHub at category: StructuredFinance;Securitisation;Cashflow homepage: https://github.com/yellowbean/Hastructure#readme bug-reports: https://github.com/yellowbean/Hastructure/issues author: Xiaoyu maintainer: always.zhang@gmail.com -copyright: 2024 Xiaoyu, Zhang +copyright: 2025 Xiaoyu, Zhang license: BSD3 license-file: LICENSE build-type: Simple @@ -66,6 +66,7 @@ library Util Validation Waterfall + WebUI other-modules: Paths_Hastructure hs-source-dirs: @@ -76,14 +77,17 @@ library , base , bytestring , containers + , deepseq , generic-lens , hashable , ieee754 , lens + , lucid , math-functions , monad-loops , numeric-limits , openapi3 + , parallel , regex-base , regex-pcre-builtin , regex-tdfa @@ -99,6 +103,7 @@ library , time , vector , wai + , yaml default-language: Haskell2010 executable Hastructure-exe @@ -118,6 +123,7 @@ executable Hastructure-exe , base-compat , bytestring , containers + , deepseq , exceptions , generic-lens , hashable @@ -130,6 +136,7 @@ executable Hastructure-exe , mtl , numeric-limits , openapi3 + , parallel , regex-base , regex-pcre-builtin , regex-tdfa @@ -187,14 +194,17 @@ test-suite Hastructure-test , base , bytestring , containers + , deepseq , generic-lens , hashable , ieee754 , lens + , lucid , math-functions , monad-loops , numeric-limits , openapi3 + , parallel , regex-base , regex-pcre-builtin , regex-tdfa @@ -214,4 +224,5 @@ test-suite Hastructure-test , time , vector , wai + , yaml default-language: Haskell2010 diff --git a/app/Main.hs b/app/Main.hs index 7ba261b8..6d311a80 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -9,19 +9,20 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE AllowAmbiguousTypes #-} module Main where import Prelude () import Prelude.Compat import System.Environment - import Control.Monad.Catch (MonadCatch, MonadThrow (..)) import Control.Monad.IO.Class (liftIO) import Control.Monad (mapM) import Control.Exception (Exception,throwIO,throw) import Control.Monad.Except import Control.Monad.Reader -import Control.Lens import Data.Aeson import Data.Aeson.Types import Data.Aeson.TH @@ -48,15 +49,12 @@ import Network.Wai.Handler.Warp import Network.Wai.Middleware.Cors import qualified Data.Aeson.Parser import Language.Haskell.TH - import Network.HTTP.Types.Status - import Servant.OpenApi import Servant import Servant.Types.SourceT (source) import Servant.API.ContentTypes (contentType) - import Types import qualified Deal as D import qualified Deal.DealBase as DB @@ -87,14 +85,12 @@ import qualified Revolving as RV import qualified Lib import qualified Util as U import qualified DateUtil as DU --- import Servant.Checked.Exceptions (NoThrow, Throws) --- import Servant.Checked.Exceptions.Internal.Servant.API (ErrStatus(toErrStatus)) - import Data.Scientific (fromRationalRepetend,formatScientific, Scientific,FPFormat(Fixed)) import Control.Lens import qualified Types as W import Cashflow (patchCumulative) +import Numeric.RootFinding import Debug.Trace debug = flip Debug.Trace.trace @@ -115,7 +111,7 @@ $(deriveJSON defaultOptions ''Version) instance ToSchema Version version1 :: Version -version1 = Version "0.41.1" +version1 = Version "0.42.5" @@ -129,6 +125,7 @@ data DealType = MDeal (DB.TestDeal AB.Mortgage) | UDeal (DB.TestDeal AB.AssetUnion) deriving(Show, Generic) + instance ToSchema CF.CashFlowFrame instance ToSchema AB.Loan instance ToSchema AB.Installment @@ -199,7 +196,6 @@ instance ToSchema CE.LiqDrawType instance ToSchema CustomDataType instance ToSchema TRG.Trigger instance ToSchema TRG.TriggerEffect -instance ToSchema DB.OverrideType instance ToSchema Types.BalanceSheetReport instance ToSchema Types.CashflowReport instance ToSchema Types.BookItem @@ -234,9 +230,10 @@ instance ToSchema AP.ExtraStress instance ToSchema AP.AssetDelinquencyAssumption instance ToSchema AP.LeaseAssetGapAssump instance ToSchema AP.LeaseAssetRentAssump - instance ToSchema Threshold - +instance ToSchema DB.DealStatFields +instance ToSchema (PerPoint Balance) +instance ToSchema (PerCurve Balance) instance ToSchema (DB.TestDeal AB.Mortgage) instance ToSchema (DB.TestDeal AB.Loan) instance ToSchema (DB.TestDeal AB.Installment) @@ -245,7 +242,6 @@ instance ToSchema (DB.TestDeal AB.Receivable) instance ToSchema (DB.TestDeal AB.ProjectedCashflow) instance ToSchema (DB.TestDeal AB.AssetUnion) instance ToSchema (DB.TestDeal AB.FixedAsset) - instance ToSchema (DB.PoolType AB.Mortgage) instance ToSchema (DB.PoolType AB.Loan) instance ToSchema (DB.PoolType AB.Installment) @@ -254,7 +250,6 @@ instance ToSchema (DB.PoolType AB.FixedAsset) instance ToSchema (DB.PoolType AB.Receivable) instance ToSchema (DB.PoolType AB.ProjectedCashflow) instance ToSchema (DB.PoolType AB.AssetUnion) - instance ToSchema (DB.UnderlyingDeal AB.Mortgage) instance ToSchema (DB.UnderlyingDeal AB.Loan) instance ToSchema (DB.UnderlyingDeal AB.Installment) @@ -263,7 +258,6 @@ instance ToSchema (DB.UnderlyingDeal AB.FixedAsset) instance ToSchema (DB.UnderlyingDeal AB.Receivable) instance ToSchema (DB.UnderlyingDeal AB.ProjectedCashflow) instance ToSchema (DB.UnderlyingDeal AB.AssetUnion) - instance ToSchema ResultComponent instance ToSchema L.PriceResult instance ToSchema DealType @@ -324,9 +318,8 @@ type RunPoolTypeRtn = Either String RunPoolTypeRtn_ patchCumulativeToPoolRun :: RunPoolTypeRtn_ -> RunPoolTypeRtn_ patchCumulativeToPoolRun = Map.map - (\(CF.CashFlowFrame _ txns,stats) -> - (CF.CashFlowFrame (0,Lib.toDate "19000101",Nothing) (CF.patchCumulative (0,0,0,0,0,0) [] txns),stats) - ) + (\(CF.CashFlowFrame _ txns,stats) -> + (CF.CashFlowFrame (0,Lib.toDate "19000101",Nothing) (CF.patchCumulative (0,0,0,0,0,0) [] txns),stats)) wrapRunPoolType :: PoolTypeWrap -> Maybe AP.ApplyAssumptionType -> Maybe [RateAssumption] -> RunPoolTypeRtn wrapRunPoolType (MPool pt) assump mRates = D.runPoolType pt assump $ Just (AP.NonPerfAssumption{AP.interest = mRates}) @@ -342,7 +335,6 @@ wrapRunPoolType x _ _ = Left $ "RunPool Failed ,due to unsupport pool type "++ s data RunAssetReq = RunAssetReq Date [AB.AssetUnion] (Maybe AP.ApplyAssumptionType) (Maybe [RateAssumption]) (Maybe PricingMethod) deriving(Show, Generic) - instance ToSchema RunAssetReq type RunAssetResp = Either String ((CF.CashFlowFrame, Map.Map CutoffFields Balance), Maybe [PriceResult]) @@ -357,7 +349,6 @@ wrapRunAsset (RunAssetReq d assets (Just (AP.PoolLevel assumps)) mRates Nothing) = do cfs <- sequenceA $ (\a -> MA.projAssetUnion a d assumps mRates) <$> assets return (P.aggPool Nothing [(cf,Map.empty) | (cf,_) <- cfs] , Nothing) - wrapRunAsset (RunAssetReq d assets (Just (AP.PoolLevel assumps)) mRates (Just pm)) = do @@ -366,7 +357,6 @@ wrapRunAsset (RunAssetReq d assets (Just (AP.PoolLevel assumps)) mRates (Just pm let assetCf = P.aggPool Nothing cfs return (assetCf , Just pricingResult) ---TODO implement on running via ByIndex type ScenarioName = String data RunDealReq = SingleRunReq DealType (Maybe AP.ApplyAssumptionType) AP.NonPerfAssumption @@ -394,7 +384,6 @@ instance ToSchema RunDateReq $(deriveJSON defaultOptions ''DealType) - $(concat <$> traverse (deriveJSON defaultOptions) [''RunDealReq, ''RunPoolReq,''RunAssetReq, ''RunDateReq,''PoolTypeWrap]) -- Swagger API @@ -402,6 +391,23 @@ type SwaggerAPI = "swagger.json" :> Get '[JSON] OpenApi type PoolRunResp = Either String (Map.Map PoolId (CF.CashFlowFrame, Map.Map CutoffFields Balance)) +data FirstLossResult = FirstLossResult Double AP.ApplyAssumptionType + | Dummyyyy + deriving(Show, Generic) + +$(deriveJSON defaultOptions ''FirstLossResult) +instance ToSchema FirstLossResult + + + +type FirstLossResp = Either String FirstLossResult +data FirstLossReq = FirstLossReq DealType AP.ApplyAssumptionType AP.NonPerfAssumption BondName + | Dummyyy + deriving(Show, Generic) + +$(deriveJSON defaultOptions ''FirstLossReq) +instance ToSchema FirstLossReq + type EngineAPI = "version" :> Get '[JSON] Version :<|> "runAsset" :> ReqBody '[JSON] RunAssetReq :> Post '[JSON] RunAssetResp :<|> "runPool" :> ReqBody '[JSON] RunPoolReq :> Post '[JSON] PoolRunResp @@ -411,6 +417,7 @@ type EngineAPI = "version" :> Get '[JSON] Version :<|> "runMultiDeals" :> ReqBody '[JSON] RunDealReq :> Post '[JSON] (Map.Map ScenarioName RunResp) :<|> "runDealByRunScenarios" :> ReqBody '[JSON] RunDealReq :> Post '[JSON] (Map.Map ScenarioName RunResp) :<|> "runByCombo" :> ReqBody '[JSON] RunDealReq :> Post '[JSON] (Map.Map String RunResp) + :<|> "runByFirstLoss" :> ReqBody '[JSON] FirstLossReq :> Post '[JSON] FirstLossResp :<|> "runDate" :> ReqBody '[JSON] RunDateReq :> Post '[JSON] [Date] @@ -433,8 +440,7 @@ showVersion :: Handler Version showVersion = return version1 runAsset :: RunAssetReq -> Handler RunAssetResp -runAsset req = return $ - wrapRunAsset req +runAsset req = return $ wrapRunAsset req runPool :: RunPoolReq -> Handler PoolRunResp runPool (SingleRunPoolReq pt passumption mRates) @@ -444,17 +450,70 @@ runPool (SingleRunPoolReq pt passumption mRates) runPoolScenarios :: RunPoolReq -> Handler (Map.Map ScenarioName PoolRunResp) runPoolScenarios (MultiScenarioRunPoolReq pt mAssumps mRates) = return $ Map.map (\assump -> - patchCumulativeToPoolRun <$> (wrapRunPoolType pt (Just assump) mRates)) - mAssumps + patchCumulativeToPoolRun <$> (wrapRunPoolType pt (Just assump) mRates)) + mAssumps runDeal :: RunDealReq -> Handler RunResp -runDeal (SingleRunReq dt assump nonPerfAssump) = return $ wrapRun dt assump nonPerfAssump +runDeal (SingleRunReq dt assump nonPerfAssump) + = return $ wrapRun dt assump nonPerfAssump + +stressAssetPerf :: Rate -> AP.AssetPerfAssumption -> AP.AssetPerfAssumption +stressAssetPerf r (AP.MortgageAssump (Just da) mp mr ms) + = AP.MortgageAssump (Just (AP.stressDefaultAssump r da)) mp mr ms +stressAssetPerf r (AP.LoanAssump (Just da) mp mr ms) + = AP.LoanAssump (Just (AP.stressDefaultAssump r da)) mp mr ms +stressAssetPerf r (AP.InstallmentAssump (Just da) mp mr ms) + = AP.InstallmentAssump (Just (AP.stressDefaultAssump r da)) mp mr ms +stressAssetPerf r (AP.ReceivableAssump (Just da) mr ms) + = AP.ReceivableAssump (Just (AP.stressDefaultAssump r da)) mr ms +stressAssetPerf _ x = x + +testByDefault :: DealType -> AP.ApplyAssumptionType -> AP.NonPerfAssumption -> BondName -> Double -> Double +testByDefault dt assumps nonPerfAssump bn r + = let + stressed = over (AP.applyAssumptionTypeAssetPerf . _1 ) (stressAssetPerf (toRational r)) assumps + runResult = wrapRun dt (Just stressed) nonPerfAssump + in + case runResult of + Right (d,_,_,_) -> + let + bMap = case d of + MDeal d' -> DB.bonds d' Map.! bn + RDeal d' -> DB.bonds d' Map.! bn + IDeal d' -> DB.bonds d' Map.! bn + LDeal d' -> DB.bonds d' Map.! bn + FDeal d' -> DB.bonds d' Map.! bn + UDeal d' -> DB.bonds d' Map.! bn + VDeal d' -> DB.bonds d' Map.! bn + PDeal d' -> DB.bonds d' Map.! bn + + bondBal = L.getOutstandingAmount bMap + in + (fromRational (toRational bondBal) - 0.01) + Left errorMsg -> 0 + + +runDealByFirstLoss :: FirstLossReq -> Handler FirstLossResp +runDealByFirstLoss (FirstLossReq dt assumps nonPerfAssump bn) + = return $ + let + itertimes = 500 + def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.0001} + in + case ridders def (1.000,500) (testByDefault dt assumps nonPerfAssump bn) of + Root r -> Right $ FirstLossResult + r + (over (AP.applyAssumptionTypeAssetPerf . _1 ) (stressAssetPerf (toRational r)) assumps) + _ -> Left "Not able to find the root" + runDealScenarios :: RunDealReq -> Handler (Map.Map ScenarioName RunResp) -runDealScenarios (MultiScenarioRunReq dt mAssumps nonPerfAssump) = return $ Map.map (\singleAssump -> wrapRun dt (Just singleAssump) nonPerfAssump) mAssumps +runDealScenarios (MultiScenarioRunReq dt mAssumps nonPerfAssump) + = return $ Map.map (\singleAssump -> wrapRun dt (Just singleAssump) nonPerfAssump) mAssumps runMultiDeals :: RunDealReq -> Handler (Map.Map ScenarioName RunResp) -runMultiDeals (MultiDealRunReq mDts assump nonPerfAssump) = return $ Map.map (\singleDealType -> wrapRun singleDealType assump nonPerfAssump) mDts +runMultiDeals (MultiDealRunReq mDts assump nonPerfAssump) + = return $ Map.map (\singleDealType -> wrapRun singleDealType assump nonPerfAssump) mDts runDate :: RunDateReq -> Handler [Date] runDate (RunDateReq sd dp md) = return $ @@ -476,7 +535,7 @@ runDealByCombo (MultiComboReq dMap assumpMap nonPerfAssumpMap) r = [ (intercalate "^" [dk,ak,nk], wrapRun d a n) | (dk,d) <- dList, (ak,a) <- aList, (nk,n) <- nList ] rMap = Map.fromList r in - return rMap -- `debug` ("RunDealByCombo->"++ show rMap) + return rMap myServer :: ServerT API Handler @@ -490,6 +549,7 @@ myServer = return engineSwagger :<|> runMultiDeals :<|> runDealByRunScenarios :<|> runDealByCombo + :<|> runDealByFirstLoss :<|> runDate @@ -502,7 +562,6 @@ data Config = Config { port :: Int} instance FromJSON Config app :: Application --- app = serve (Proxy :: Proxy API) myServer app = simpleCors $ serve (Proxy :: Proxy API) myServer diff --git a/package.yaml b/package.yaml index e9e93e76..ca7db950 100644 --- a/package.yaml +++ b/package.yaml @@ -1,10 +1,10 @@ name: Hastructure -version: 0.41.1 +version: 0.41.3 github: "yellowbean/Hastructure" license: BSD3 author: "Xiaoyu" maintainer: "always.zhang@gmail.com" -copyright: "2024 Xiaoyu, Zhang" +copyright: "2025 Xiaoyu, Zhang" extra-source-files: - README.md @@ -44,10 +44,14 @@ dependencies: - openapi3 - swagger2 - split +- yaml +- lucid - tabular - numeric-limits - scientific - math-functions +- parallel +- deepseq library: source-dirs: @@ -88,11 +92,7 @@ executables: - exceptions - tabular - math-functions - # - servant-errors - # - servant-exceptions - servant-checked-exceptions - # - jsonschema-gen - # - timeseries tests: Hastructure-test: diff --git a/src/Accounts.hs b/src/Accounts.hs index e410bdf5..18550f41 100644 --- a/src/Accounts.hs +++ b/src/Accounts.hs @@ -2,6 +2,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE DeriveAnyClass #-} module Accounts (Account(..),ReserveAmount(..),draw,deposit ,transfer,depositInt ,InterestInfo(..),buildEarnIntAction @@ -20,11 +21,11 @@ import Data.Aeson.TH import Data.Aeson.Types import GHC.Generics import Control.Lens.Tuple - import Control.Lens hiding (Index) - import qualified InterestRate as IR +-- import Web.Hyperbole + import Debug.Trace debug = flip trace @@ -84,20 +85,19 @@ accrueInt endDate a@(Account bal _ (Just interestType) _ stmt) depositInt :: Date -> Account -> Account depositInt _ a@(Account _ _ Nothing _ _) = a depositInt ed a@(Account bal _ (Just intType) _ stmt) - = a {accBalance = newBal ,accStmt= appendStmt newTxn stmt ,accInterest = Just (newIntInfoType intType)} - where - -- accruedInt = accrueInt a (mkTs [(lastCollectDate, toRational r),(ed, toRational r)]) ed - accruedInt = accrueInt ed a - newIntInfoType (BankAccount x y _d) = BankAccount x y ed - newIntInfoType (InvestmentAccount x y z z1 _d z2) = (InvestmentAccount x y z z1 ed z2) - newBal = accruedInt + bal -- `debug` ("INT ACC->"++ show accrued_int) - newTxn = AccTxn ed newBal accruedInt BankInt + = a {accBalance = newBal ,accStmt= appendStmt newTxn stmt ,accInterest = Just (newIntInfoType intType)} + where + accruedInt = accrueInt ed a + newIntInfoType (BankAccount x y _d) = BankAccount x y ed + newIntInfoType (InvestmentAccount x y z z1 _d z2) = InvestmentAccount x y z z1 ed z2 + newBal = accruedInt + bal + newTxn = AccTxn ed newBal accruedInt BankInt -- | move cash from account A to account B transfer :: (Account,Account) -> Date -> Amount -> (Account, Account) transfer (sourceAcc@(Account sBal san _ _ sStmt), targetAcc@(Account tBal tan _ _ tStmt)) - d - amount + d + amount = (sourceAcc {accBalance = newSBal, accStmt = sourceNewStmt} ,targetAcc {accBalance = newTBal, accStmt = targetNewStmt}) where diff --git a/src/Analytics.hs b/src/Analytics.hs index 33d93b82..ace4d361 100644 --- a/src/Analytics.hs +++ b/src/Analytics.hs @@ -3,7 +3,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ScopedTypeVariables #-} -module Analytics (calcDuration,pv,calcWAL,pv2,pv3,fv2,pv21,calcRequiredAmtForIrrAtDate) +module Analytics (calcConvexity,calcDuration,pv,calcWAL,pv2,pv3,fv2,pv21,calcRequiredAmtForIrrAtDate) where import Types @@ -32,18 +32,33 @@ calcWAL th bal d ps = in sum weightedAmts / bal -calcDuration :: Date -> [(Date,Balance)] -> Ts -> Balance -calcDuration d ps pricingCurve +calcDuration :: DayCount -> Date -> [(Date,Balance)] -> Ts -> Rate +calcDuration dc d ps pricingCurve = (foldr (\(_d,_b) acc -> - (mulBR - ((pv pricingCurve d _d _b) / presentValue) - (yearCountFraction DC_ACT_365F d _d)) + (*) + (divideBB (pv pricingCurve d _d _b) presentValue) + (yearCountFraction dc d _d) + acc) - 0 + 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 @@ -102,10 +117,8 @@ calcRequiredAmtForIrrAtDate :: Double -> [Date] -> [Amount] -> Date -> Maybe Amo calcRequiredAmtForIrrAtDate irr [] _ d = Nothing calcRequiredAmtForIrrAtDate irr ds vs d = let - def = RiddersParam - { riddersMaxIter = 200 - , riddersTol = RelTol 0.00000001 - } + 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)) diff --git a/src/Asset.hs b/src/Asset.hs index 387d5679..bcefd5ce 100644 --- a/src/Asset.hs +++ b/src/Asset.hs @@ -80,6 +80,12 @@ class (Show a,IR.UseRate a) => Asset a where -- | get number of remaining payments getRemainTerms :: a -> Int -- | project asset cashflow under credit stress and interest assumptions + getTotalTerms :: a -> Int + getTotalTerms a = ACM.originTerm (getOriginInfo a) + + getPastTerms :: a -> Int + getPastTerms a = getTotalTerms a - getRemainTerms a + projCashflow :: a -> Date -> A.AssetPerf -> Maybe [RateAssumption] -> Either String (CF.CashFlowFrame, Map.Map CutoffFields Balance) -- | Get possible number of borrower getBorrowerNum :: a -> Int @@ -156,55 +162,87 @@ applyExtraStress (Just ExtraStress{A.defaultFactors= mDefFactor ,getTsVals $ multiplyTs Exc (zipTs ds def) defFactor) -buildPrepayRates :: [Date] -> Maybe A.AssetPrepayAssumption -> Either String [Rate] -buildPrepayRates ds Nothing = Right $ replicate (pred (length ds)) 0.0 -buildPrepayRates ds mPa = - case mPa of - Just (A.PrepaymentConstant r) -> Right $ replicate size r - Just (A.PrepaymentCPR r) -> Right $ Util.toPeriodRateByInterval r <$> getIntervalDays ds - Just (A.PrepaymentVec vs) -> Right $ zipWith - Util.toPeriodRateByInterval - (paddingDefault 0.0 vs (pred size)) - (getIntervalDays ds) - Just (A.PrepaymentVecPadding vs) -> Right $ zipWith - Util.toPeriodRateByInterval - (paddingDefault (last vs) vs (pred size)) - (getIntervalDays ds) - Just (A.PrepayStressByTs ts x) -> - do - rs <- buildPrepayRates ds (Just x) - return $ getTsVals $ multiplyTs Exc (zipTs (tail ds) rs) ts - - _ -> Left ("failed to find prepayment type"++ show mPa) - where - size = length ds +cpr2smm :: Rate -> Rate +cpr2smm r = toRational $ 1 - (1 - fromRational r :: Double) ** (1/12) -buildDefaultRates :: [Date] -> Maybe A.AssetDefaultAssumption -> Either String [Rate] -buildDefaultRates ds Nothing = Right $ replicate (pred (length ds)) 0.0 -buildDefaultRates ds mDa = - case mDa of - Just (A.DefaultConstant r) -> Right $ replicate size r - Just (A.DefaultCDR r) -> Right $ Util.toPeriodRateByInterval r <$> getIntervalDays ds - Just (A.DefaultVec vs) -> Right $ zipWith - Util.toPeriodRateByInterval - (paddingDefault 0.0 vs (pred size)) - (getIntervalDays ds) - Just (A.DefaultVecPadding vs) -> Right $ zipWith +buildPrepayRates :: Asset b => b -> [Date] -> Maybe A.AssetPrepayAssumption -> Either String [Rate] +buildPrepayRates _ ds Nothing = Right $ replicate (pred (length ds)) 0.0 +buildPrepayRates a ds mPa = + capWith 1.0 <$> + case mPa of + Just (A.PrepaymentConstant r) -> Right $ replicate size r + Just (A.PrepaymentCPR r) -> Right $ Util.toPeriodRateByInterval r <$> getIntervalDays ds + Just (A.PrepaymentVec vs) -> Right $ zipWith Util.toPeriodRateByInterval - (paddingDefault (last vs) vs (pred size)) + (paddingDefault 0.0 vs (pred size)) (getIntervalDays ds) - Just (A.DefaultAtEndByRate r rAtEnd) - -> Right $ case size of - 0 -> [] - 1 -> [] - _ -> (Util.toPeriodRateByInterval r <$> getIntervalDays (init ds)) ++ (Util.toPeriodRateByInterval rAtEnd <$> getIntervalDays [head ds,last ds]) - - Just (A.DefaultStressByTs ts x) -> - do - rs <- buildDefaultRates ds (Just x) - let r = getTsVals $ multiplyTs Inc (zipTs (tail ds) rs) ts - return r - _ -> Left ("failed to find default rate type"++ show mDa) + Just (A.PrepaymentVecPadding vs) -> Right $ zipWith + Util.toPeriodRateByInterval + (paddingDefault (last vs) vs (pred size)) + (getIntervalDays ds) + Just (A.PrepayStressByTs ts x) -> + do + rs <- buildPrepayRates a ds (Just x) + return $ getTsVals $ multiplyTs Exc (zipTs (tail ds) rs) ts + Just (A.PrepaymentPSA r) -> + let + agedTerm = getPastTerms a + remainingTerm = getRemainTerms a + ppyVectorInCPR = (* r) <$> [0.002,0.004..0.06] ++ repeat 0.06 + vectorUsed = take remainingTerm $ drop agedTerm ppyVectorInCPR + in + case period (getOriginInfo a) of + Monthly -> Right $ cpr2smm <$> vectorUsed + _ -> Left $ "PSA is only supported for monthly payment but got "++ show (period (getOriginInfo a)) + Just (A.PrepaymentByTerm rs) -> + let + agedTerm = getPastTerms a + oTerm = originTerm (getOriginInfo a) + in + case find (\x -> oTerm == length x) rs of + Just v -> Right $ drop agedTerm v + Nothing -> Left "Prepayment by term doesn't match the origin term" + + _ -> Left ("failed to find prepayment type"++ show mPa) + where + size = length ds + +buildDefaultRates :: Asset b => b -> [Date] -> Maybe A.AssetDefaultAssumption -> Either String [Rate] +buildDefaultRates _ ds Nothing = Right $ replicate (pred (length ds)) 0.0 +buildDefaultRates a ds mDa = + capWith 1.0 <$> + case mDa of + Just (A.DefaultConstant r) -> Right $ replicate size r + Just (A.DefaultCDR r) -> Right $ Util.toPeriodRateByInterval r <$> getIntervalDays ds + Just (A.DefaultVec vs) -> Right $ zipWith + Util.toPeriodRateByInterval + (paddingDefault 0.0 vs (pred size)) + (getIntervalDays ds) + Just (A.DefaultVecPadding vs) -> Right $ zipWith + Util.toPeriodRateByInterval + (paddingDefault (last vs) vs (pred size)) + (getIntervalDays ds) + Just (A.DefaultAtEndByRate r rAtEnd) + -> Right $ case size of + 0 -> [] + 1 -> [] + _ -> (Util.toPeriodRateByInterval r <$> getIntervalDays (init ds)) ++ (Util.toPeriodRateByInterval rAtEnd <$> getIntervalDays [head ds,last ds]) + + Just (A.DefaultStressByTs ts x) -> + do + rs <- buildDefaultRates a ds (Just x) + let r = getTsVals $ multiplyTs Inc (zipTs (tail ds) rs) ts + return r + + Just (A.DefaultByTerm rs) -> + let + agedTerm = getPastTerms a + oTerm = originTerm (getOriginInfo a) + in + case find (\x -> oTerm == length x) rs of + Just v -> Right $ drop agedTerm v + Nothing -> Left "Default by term doesn't match the origin term" + _ -> Left ("failed to find default rate type"++ show mDa) where size = length ds @@ -213,17 +251,17 @@ getRecoveryLagAndRate Nothing = (0,0) getRecoveryLagAndRate (Just (A.Recovery (r,lag))) = (r,lag) -- | build pool assumption rate (prepayment, defaults, recovery rate , recovery lag) -buildAssumptionPpyDefRecRate :: [Date] -> A.AssetPerfAssumption -> Either String ([Rate],[Rate],Rate,Int) -buildAssumptionPpyDefRecRate ds (A.LoanAssump mDa mPa mRa mESa) = buildAssumptionPpyDefRecRate ds (A.MortgageAssump mDa mPa mRa mESa) -buildAssumptionPpyDefRecRate ds (A.MortgageAssump mDa mPa mRa mESa) +buildAssumptionPpyDefRecRate :: Asset a => a -> [Date] -> A.AssetPerfAssumption -> Either String ([Rate],[Rate],Rate,Int) +buildAssumptionPpyDefRecRate a ds (A.LoanAssump mDa mPa mRa mESa) = buildAssumptionPpyDefRecRate a ds (A.MortgageAssump mDa mPa mRa mESa) +buildAssumptionPpyDefRecRate a ds (A.MortgageAssump mDa mPa mRa mESa) = let size = length ds zeros = replicate size 0.0 (recoveryRate,recoveryLag) = getRecoveryLagAndRate mRa in do - prepayRates <- buildPrepayRates ds mPa - defaultRates <- buildDefaultRates ds mDa + prepayRates <- buildPrepayRates a ds mPa + defaultRates <- buildDefaultRates a ds mDa let (prepayRates2,defaultRates2) = applyExtraStress mESa ds prepayRates defaultRates return (prepayRates2,defaultRates2,recoveryRate,recoveryLag) @@ -239,9 +277,9 @@ getDefaultLagAndRate Nothing = (0,0) getDefaultLagAndRate (Just (A.Recovery (r,lag))) = (r,lag) -- | build prepayment rates/ delinq rates and (%,lag) convert to default, recovery rate, recovery lag -buildAssumptionPpyDelinqDefRecRate :: [Date] -> A.AssetPerfAssumption -> Either String ([Rate],[Rate],(Rate,Lag),Rate,Int) -buildAssumptionPpyDelinqDefRecRate ds (A.MortgageDeqAssump mDeqDefault mPa mRa (Just _)) = Left "Delinq assumption doesn't support extra stress" -buildAssumptionPpyDelinqDefRecRate ds (A.MortgageDeqAssump mDeqDefault mPa mRa Nothing) +buildAssumptionPpyDelinqDefRecRate :: Asset a => a -> [Date] -> A.AssetPerfAssumption -> Either String ([Rate],[Rate],(Rate,Lag),Rate,Int) +buildAssumptionPpyDelinqDefRecRate _ ds (A.MortgageDeqAssump mDeqDefault mPa mRa (Just _)) = Left "Delinq assumption doesn't support extra stress" +buildAssumptionPpyDelinqDefRecRate a ds (A.MortgageDeqAssump mDeqDefault mPa mRa Nothing) = let (recoveryRate,recoveryLag) = getRecoveryLagAndRate mRa zeros = replicate (length ds) 0.0 @@ -253,7 +291,7 @@ buildAssumptionPpyDelinqDefRecRate ds (A.MortgageDeqAssump mDeqDefault mPa mRa N ,pct) in do - prepayRates <- buildPrepayRates ds mPa + prepayRates <- buildPrepayRates a ds mPa return (prepayRates,delinqRates,(defaultPct,defaultLag),recoveryRate, recoveryLag) @@ -287,9 +325,10 @@ priceAsset m d (PVCurve curve) assumps mRates cType Inc -> txns) pv = pv3 curve d ds amts -- `debug` ("pricing"++ show d++ show ds++ show amts) wal = calcWAL ByYear cb d (zip amts ds) - duration = calcDuration d (zip ds amts) curve + duration = fromRational $ calcDuration DC_ACT_365F d (zip ds amts) curve + convexity = fromRational $ calcConvexity DC_ACT_365F d (zip ds amts) curve in - Right $ AssetPrice pv wal duration (-1) accruedInt + Right $ AssetPrice pv wal duration convexity accruedInt Left x -> Left x priceAsset m d (BalanceFactor currentFactor defaultedFactor) assumps mRates cType @@ -306,7 +345,7 @@ priceAsset m d (BalanceFactor currentFactor defaultedFactor) assumps mRates cTyp amts = CF.tsTotalCash <$> txns wal = calcWAL ByYear cb d (zip amts ds) -- `debug` ("pricing"++ show d++ show ds++ show amts) in - Right $ AssetPrice val wal (-1) (-1) (-1) --TODO missing convixity + Right $ AssetPrice val wal (-1) (-1) (-1) Left x -> Left x priceAsset m d (PvRate r) assumps mRates cType @@ -331,7 +370,8 @@ priceAsset m d (PvRate r) assumps mRates cType wal = calcWAL ByYear cb d (zip amts ds) pv = sum $ zipWith (pv2 r d) ds amts curve = mkTs $ zip ds (repeat (toRational r)) - duration = calcDuration d (zip ds amts) curve + duration = fromRational $ calcDuration DC_ACT_365F d (zip ds amts) curve + convexity = fromRational $ calcConvexity DC_ACT_365F d (zip ds amts) curve in - Right $ AssetPrice pv wal duration (-1) accruedInt --TODO missing convixity + Right $ AssetPrice pv wal duration convexity accruedInt Left x -> Left x diff --git a/src/AssetClass/AssetBase.hs b/src/AssetClass/AssetBase.hs index 5f46c5a2..f1b48fe2 100644 --- a/src/AssetClass/AssetBase.hs +++ b/src/AssetClass/AssetBase.hs @@ -3,6 +3,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE BangPatterns #-} module AssetClass.AssetBase (Installment(..),Lease(..),OriginalInfo(..),Status(..) @@ -52,15 +53,24 @@ data AmortPlan = Level -- ^ for mortgage / french system -> -- | calculate period payment (Annuity/Level mortgage) calcPmt :: Balance -> IRate -> Int -> Amount -calcPmt bal 0.0 periods = divideBI bal periods -calcPmt bal periodRate periods = - let - periodRate1 = toRational periodRate - r1 = ((1+periodRate1)^^periods) / ((1+periodRate1)^^periods-1) -- `debug` ("PR>>"++show periodRate) - pmtFactor = periodRate1 * r1 -- `debug` ("R1>>"++ show r1) - in - mulBR bal pmtFactor -- `debug` ("Factor"++ show pmtFactor) - +-- calcPmt bal 0.0 periods = divideBI bal periods +-- calcPmt bal periodRate periods = +-- let +-- periodRate1 = toRational periodRate +-- r1 = ((1+periodRate1)^^periods) / ((1+periodRate1)^^periods-1) -- `debug` ("PR>>"++show periodRate) +-- pmtFactor = periodRate1 * r1 -- `debug` ("R1>>"++ show r1) +-- in +-- mulBR bal pmtFactor -- `debug` ("Factor"++ show pmtFactor) + +-- Generate by gork +calcPmt bal rate periods | rate == 0.0 = divideBI bal periods + | otherwise = + let rate' = realToFrac rate :: Double + logBase = log (1 + rate') + num = exp (logBase * fromIntegral periods) + den = num - 1 + r1 = num / den + in mulBR (realToFrac bal) (toRational (rate' * r1)) type InterestAmount = Amount type PrincipalAmount = Amount diff --git a/src/AssetClass/AssetCashflow.hs b/src/AssetClass/AssetCashflow.hs index 6c0214cb..9b6ac69c 100644 --- a/src/AssetClass/AssetCashflow.hs +++ b/src/AssetClass/AssetCashflow.hs @@ -4,7 +4,7 @@ module AssetClass.AssetCashflow (applyHaircut,patchPrepayPenaltyFlow,getRecoveryLag,decreaseBorrowerNum - ,patchLossRecovery) + ,patchLossRecovery,getRecoveryLagFromAssumption) where import qualified Data.Time as T @@ -129,6 +129,15 @@ getRecoveryLag :: A.RecoveryAssumption -> Int getRecoveryLag (A.Recovery (_,lag)) = lag getRecoveryLag (A.RecoveryTiming (_,rs)) = length rs +getRecoveryLagFromAssumption :: A.AssetPerfAssumption -> Maybe Int +getRecoveryLagFromAssumption (A.MortgageAssump _ _ (Just ra) _) = Just $ getRecoveryLag ra +getRecoveryLagFromAssumption (A.MortgageDeqAssump _ _ (Just ra) _) = Just $ getRecoveryLag ra +getRecoveryLagFromAssumption (A.LoanAssump _ _ (Just ra) _) = Just $ getRecoveryLag ra +getRecoveryLagFromAssumption (A.InstallmentAssump _ _ (Just ra) _) = Just $ getRecoveryLag ra +getRecoveryLagFromAssumption (A.ReceivableAssump _ (Just ra) _) = Just $ getRecoveryLag ra +getRecoveryLagFromAssumption _ = Nothing + + decreaseBorrowerNum :: Balance -> Balance -> Maybe BorrowerNum -> Maybe Int decreaseBorrowerNum bb 0 mBn = Nothing decreaseBorrowerNum bb eb mBn diff --git a/src/AssetClass/Installment.hs b/src/AssetClass/Installment.hs index 6cb8e98f..3776391e 100644 --- a/src/AssetClass/Installment.hs +++ b/src/AssetClass/Installment.hs @@ -32,6 +32,8 @@ 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 @@ -47,7 +49,7 @@ projectInstallmentFlow (startBal, lastPaidDate, (originRepay,originInt), startRa foldl (\(acc,factor) (pDate, ppyRate, defRate, rt) -> let - begBal = CF.mflowBalance (last acc) + begBal = view CF.tsRowBalance (last acc) newDefault = mulBR begBal defRate newPrepay = mulBR (begBal - newDefault) ppyRate intBal = begBal - newDefault - newPrepay @@ -146,8 +148,8 @@ instance Asset Installment where currentFactor = divideBB cb currentScheduleBal in do - ppyRates <- Ast.buildPrepayRates (lastPayDate:cfDates) prepayAssump - defRates <- Ast.buildDefaultRates (lastPayDate:cfDates) defaultAssump + 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 txns recoveryAssump) let begBal = CF.buildBegBal futureTxns diff --git a/src/AssetClass/Lease.hs b/src/AssetClass/Lease.hs index 7a640fe7..9d1ba857 100644 --- a/src/AssetClass/Lease.hs +++ b/src/AssetClass/Lease.hs @@ -96,17 +96,6 @@ nextLeaseTill l (rsc,tc,mg) lastDate ed accum where (new_lease,new_lastDate) = nextLease l (rsc,tc,mg) --- extractAssump :: [AP.AssumptionBuilder] -> (Rate,Ts,([(Amount,Int)],Int),DayGap,Date)-> (Rate,Ts,([(Amount,Int)],Int),DayGap,Date) --- extractAssump [] r = r --- extractAssump (ap:aps) (a,b,c,d,e) --- = case ap of --- (AP.LeaseProjectionEnd ed) -> extractAssump aps (a,b,c,d,ed) --- (AP.LeaseGapDays mg) -> extractAssump aps (a,b,c,mg,e) --- (AP.LeaseBaseAnnualRate r) -> extractAssump aps (r,b,c,d,e) --- (AP.LeaseBaseCurve ts) -> extractAssump aps (a,ts,c,d,e) --- (AP.LeaseGapDaysByAmount tbl rest) -> extractAssump aps (a,b,(tbl,rest),d,e) --- _ -> extractAssump aps (a,b,c,d,e) - getGapDaysByBalance :: Lease -> ([(Amount,Int)],Int) -> Int getGapDaysByBalance l tbl@(rows,defaultVal) = let diff --git a/src/AssetClass/Loan.hs b/src/AssetClass/Loan.hs index 4a63a0a4..60b15c4a 100644 --- a/src/AssetClass/Loan.hs +++ b/src/AssetClass/Loan.hs @@ -30,6 +30,8 @@ import AssetClass.AssetCashflow import Debug.Trace import Assumptions (AssetDefaultAssumption(DefaultCDR)) import qualified Asset as A +import Control.Lens hiding (element) +import Control.Lens.TH debug = flip trace @@ -42,7 +44,7 @@ projectLoanFlow ((originBal,ot,or), startBal, lastPayDate, pt, dc,startRate, beg foldl (\(acc,factor) (pDate, ppyRate, defRate, intRate, rt) -> let - begBal = CF.mflowBalance (last acc) + begBal = view CF.tsRowBalance (last acc) lastPaidDate = getDate (last acc) newDefault = mulBR begBal defRate newPrepay = mulBR (begBal - newDefault) ppyRate @@ -133,8 +135,8 @@ instance Asset Loan where in do rateVector <- A.projRates cr or mRate cfDates - ppyRates <- A.buildPrepayRates (lastPayDate:cfDates) prepayAssump - defRates <- A.buildDefaultRates (lastPayDate:cfDates) defaultAssump + ppyRates <- A.buildPrepayRates pl (lastPayDate:cfDates) prepayAssump + defRates <- A.buildDefaultRates pl (lastPayDate:cfDates) defaultAssump let dc = getDayCount or let remainTerms = reverse $ replicate recoveryLag 0 ++ [0..rt] -- `debug` ("rateVector"++show rateVector) let initFactor = case prinPayType of diff --git a/src/AssetClass/Mortgage.hs b/src/AssetClass/Mortgage.hs index 7e6800c4..696840ba 100644 --- a/src/AssetClass/Mortgage.hs +++ b/src/AssetClass/Mortgage.hs @@ -33,6 +33,8 @@ import Debug.Trace import Assumptions (AssetPerfAssumption(MortgageAssump)) import GHC.Float.RealFracMethods (truncateFloatInteger) import Cashflow (extendTxns) +import Control.Lens hiding (element) +import Control.Lens.TH debug = flip trace projectMortgageFlow :: (Balance, Balance, Date, Maybe BorrowerNum, AmortPlan, DayCount, IRate, Period, Int) -> (Dates, [DefaultRate],[PrepaymentRate],[IRate],[Int]) -> ([CF.TsRow], Balance) @@ -43,9 +45,9 @@ projectMortgageFlow (originBal, startBal, lastPayDate, mbn, pt, dc, startRate, p foldl (\(acc,lastOriginBal) (pDate, defRate, ppyRate, intRate, rt) -> let - begBal = CF.mflowBalance (last acc) + begBal = view CF.tsRowBalance (last acc) lastPaidDate = getDate (last acc) -- `debug` ("beg bal"++ show begBal) - newDefault = mulBR begBal defRate + newDefault = mulBR begBal defRate -- `debug` ("new default"++ show defRate++ ">>"++ show begBal) newPrepay = mulBR (begBal - newDefault) ppyRate -- performing balance _balAfterPpy = begBal - newDefault - newPrepay -- `debug` ("new ppy "++ show newPrepay ++ "beg bal"++ show (begBal - newDefault) ++ "ppy rate"++ show ppyRate) @@ -182,7 +184,7 @@ projectScheduleDelinqFlow (trs,backToPerfCfs) surviveRate begBal (flow:flows) (d ppyAmt = mulBR (begBal - delinqAmt) ppyRate -- `debug` ("begbal"++ show begBal++">>"++ show delinqAmt) newSurviveRate = (1-delinqRate) * (1-ppyRate) * surviveRate - scheduleBal = CF.mflowBalance flow + scheduleBal = view CF.tsRowBalance flow schedulePrin = mulBR (CF.mflowPrincipal flow) surviveRate scheduleInt = mulBR (CF.mflowInterest flow) surviveRate @@ -193,7 +195,7 @@ projectScheduleDelinqFlow (trs,backToPerfCfs) surviveRate begBal (flow:flows) (d splitPct = divideBB (mulBR delinqAmt (1-defaultPct)) begBal perfFlows = take (length flows - defaultLag - recoveryLag + 1) $ CF.splitTrs splitPct (flow:flows) in - [ CF.tsSetDate f d | (d,f) <- zip futureDs perfFlows ] + [ set CF.tsDate d f | (d,f) <- zip futureDs perfFlows ] newDefaultBals = replace defaultBals (pred defaultLag) newDefaultBal newRecoveryBals = replace recoveryBals (recoveryLag + pred defaultLag) (mulBR newDefaultBal recoveryRate) @@ -210,7 +212,7 @@ projCashflowByDefaultAmt (cb,lastPayDate,pt,p,cr,mbn) (cfDates,(expectedDefaultB foldl (\acc (pDate, (defaultBal,futureDefualtBal), ppyRate, rate, rt) -> let - begBal = CF.mflowBalance (last acc) + begBal = view CF.tsRowBalance (last acc) mBorrower = CF.mflowBorrowerNum (last acc) newDefault = if begBal <= (defaultBal+futureDefualtBal) then begBal @@ -244,7 +246,7 @@ calcScheduleBalaceToday m mRates asOfDay case calcCashflow (resetToOrig m) sd mRates of Right (CF.CashFlowFrame _ scheduleTxn) -> case getByDate asOfDay scheduleTxn of - Just f -> CF.mflowBalance f + Just f -> view CF.tsRowBalance f Nothing -> error "Failed to find schedule balance" Left _ -> 0 @@ -260,7 +262,7 @@ projScheduleCashflowByDefaultAmt (cb,lastPayDate,cr,mbn) (scheduleFlows,(expecte -> let pDate = getDate cflow - begBal = CF.mflowBalance (last acc) + begBal = view CF.tsRowBalance (last acc) mBorrower = CF.mflowBorrowerNum (last acc) newDefault = if begBal <= (defaultBal+futureDefualtBal) then @@ -308,7 +310,7 @@ instance Ast.Asset Mortgage where = fst <$> (projCashflow m d (MortgageAssump Nothing Nothing Nothing Nothing,A.DummyDelinqAssump,A.DummyDefaultAssump) mRates) calcCashflow s@(ScheduleMortgageFlow beg_date flows _) d _ - = Right $ CF.CashFlowFrame ( (CF.mflowBalance . head) flows, beg_date, Nothing ) flows + = Right $ CF.CashFlowFrame ( ((view CF.tsRowBalance) . head) flows, beg_date, Nothing ) flows calcCashflow m@(AdjustRateMortgage _origin _arm _bal _rate _term _mbn _status) d mRates = Left $ "to be implement on adjust rate mortgage" @@ -350,10 +352,9 @@ instance Ast.Asset Mortgage where getPaymentDates (Mortgage (MortgageOriginalInfo _ _ ot p sd _ _ _) _ _ _ _ _) extra = genDates sd p (ot+extra) getPaymentDates (AdjustRateMortgage (MortgageOriginalInfo _ _ ot p sd _ _ _) _ _ _ _ _ _) extra = genDates sd p (ot+extra) getPaymentDates (ScheduleMortgageFlow begDate flows dp) extra - = - let + = let lastPayDay = (getDate . last) flows - extDates =genSerialDates dp Exc lastPayDay extra + extDates = genSerialDates dp Exc lastPayDay extra in getDates flows ++ extDates @@ -391,7 +392,7 @@ instance Ast.Asset Mortgage where in do rateVector <- A.projRates cr or mRates cfDates - ppyRates <- Ast.buildPrepayRates (lastPayDate:cfDates) amp + ppyRates <- Ast.buildPrepayRates m (lastPayDate:cfDates) amp let txns = projCashflowByDefaultAmt (cb,lastPayDate,prinPayType,p,cr,mbn) (cfDates,(expectedDefaultBals,unAppliedDefaultBals),ppyRates,rateVector,remainTerms) let (futureTxns,historyM)= CF.cutoffTrs asOfDay (patchLossRecovery txns amr) @@ -417,13 +418,13 @@ instance Ast.Asset Mortgage where remainTerms = paddingDefault 0 (reverse [0..(length cfDates - recoveryLag)]) (length cfDates) in do - ppyRates <- Ast.buildPrepayRates (lastPayDate:cfDates) amp + ppyRates <- Ast.buildPrepayRates m (lastPayDate:cfDates) amp let txns = projCashflowByDefaultAmt (cb,lastPayDate,prinPayType,p,cr,mbn) (cfDates,(expectedDefaultBals,unAppliedDefaultBals),ppyRates,rateVector,remainTerms) let (futureTxns,historyM)= CF.cutoffTrs asOfDay (patchLossRecovery txns amr) let begBal = CF.buildBegBal futureTxns return $ (applyHaircut ams $ patchPrepayPenaltyFlow (ot,mpn) (CF.CashFlowFrame (begBal,asOfDay,Nothing) futureTxns) ,historyM) -- project schedule cashflow with total default amount - projCashflow (ScheduleMortgageFlow begDate flows dp) asOfDay + projCashflow m@(ScheduleMortgageFlow begDate flows dp) asOfDay assumps@(pAssump@(A.MortgageAssump (Just (A.DefaultByAmt (dBal,vs))) amp amr ams ),dAssump,fAssump) _ = let begBal = CF.mflowBegBalance $ head flows @@ -441,7 +442,7 @@ instance Ast.Asset Mortgage where flowsWithEx = flows ++ extendTxns (last flows) extraDates -- `debug` (">> end date"++ show endDate++">>> extra dates"++show extraDates) in do - _ppyRate <- Ast.buildPrepayRates (begDate:originCfDates) amp + _ppyRate <- Ast.buildPrepayRates m (begDate:originCfDates) amp let ppyRates = paddingDefault 0.0 _ppyRate totalLength let (txns,_) = projScheduleCashflowByDefaultAmt (begBal,begDate,begRate,begMbn) @@ -465,8 +466,8 @@ instance Ast.Asset Mortgage where in do rateVector <- A.projRates cr or mRates cfDates - defRates <- Ast.buildDefaultRates (lastPayDate:cfDates) amd - ppyRates <- Ast.buildPrepayRates (lastPayDate:cfDates) amp + defRates <- Ast.buildDefaultRates m (lastPayDate:cfDates) amd + ppyRates <- Ast.buildPrepayRates m (lastPayDate:cfDates) amp let (txns,_) = projectMortgageFlow (ob, cb,lastPayDate,mbn,prinPayType,dc,cr,p,ot) (cfDates, defRates, ppyRates,rateVector,remainTerms) @@ -493,7 +494,7 @@ instance Ast.Asset Mortgage where in do rateVector <- A.projRates cr or mRates cfDates - (ppyRates,delinqRates,(_,_),_,_) <- Ast.buildAssumptionPpyDelinqDefRecRate (lastPayDate:cfDates) (A.MortgageDeqAssump amd amp amr ams) + (ppyRates,delinqRates,(_,_),_,_) <- Ast.buildAssumptionPpyDelinqDefRecRate m (lastPayDate:cfDates) (A.MortgageDeqAssump amd amp amr ams) let txns = projectDelinqMortgageFlow ([],[]) cb mbn lastPayDate cfDates delinqRates ppyRates rateVector (defaultPct,defaultLag,recoveryRate,recoveryLag,p,prinPayType,ot) (replicate cfDatesLength 0.0,replicate cfDatesLength 0.0,replicate cfDatesLength 0.0) @@ -543,7 +544,7 @@ instance Ast.Asset Mortgage where dc = getDayCount or in do - (ppyRates,defRates,recoveryRate,recoveryLag) <- buildAssumptionPpyDefRecRate (lastPayDate:cfDates) (A.MortgageAssump amd amp amr ams) + (ppyRates,defRates,recoveryRate,recoveryLag) <- buildAssumptionPpyDefRecRate m (lastPayDate:cfDates) (A.MortgageAssump amd amp amr ams) let remainTerms = reverse $ replicate recoveryLag 0 ++ [0..rt] let (txns,_) = projectMortgageFlow (scheduleBalToday, cb,lastPayDate,mbn,prinPayType,dc,cr,p,ot) (cfDates, defRates, ppyRates,rateVector,remainTerms) let (futureTxns,historyM)= CF.cutoffTrs asOfDay (patchLossRecovery txns amr) @@ -568,7 +569,7 @@ instance Ast.Asset Mortgage where rateVector = fromRational <$> getValByDates rateCurve Inc cfDates -- `debug` ("RateCurve"++ show rate_curve) in do - (ppyRates, delinqRates,(_,_),_,_) <- Ast.buildAssumptionPpyDelinqDefRecRate (lastPayDate:cfDates) (A.MortgageDeqAssump amd amp amr ams) + (ppyRates, delinqRates,(_,_),_,_) <- Ast.buildAssumptionPpyDelinqDefRecRate m (lastPayDate:cfDates) (A.MortgageDeqAssump amd amp amr ams) let txns = projectDelinqMortgageFlow ([],[]) cb mbn lastPayDate cfDates delinqRates ppyRates rateVector (defaultPct,defaultLag,recoveryRate,recoveryLag,p,prinPayType,ot) (replicate cfDatesLength 0.0,replicate cfDatesLength 0.0,replicate cfDatesLength 0.0) @@ -577,7 +578,7 @@ instance Ast.Asset Mortgage where return $ (applyHaircut ams $ patchPrepayPenaltyFlow (ot,mpn) (CF.CashFlowFrame (begBal,asOfDay,Nothing) futureTxns) ,historyM) -- schedule mortgage flow without delinq - projCashflow (ScheduleMortgageFlow begDate flows dp) asOfDay + projCashflow m@(ScheduleMortgageFlow begDate flows dp) asOfDay assumps@(pAssump@(A.MortgageAssump _ _ mRa ams ),dAssump,fAssump) _ = let begBal = CF.mflowBegBalance $ head flows @@ -588,7 +589,7 @@ instance Ast.Asset Mortgage where cfDates = (CF.getDate <$> flows) ++ extraDates in do - (ppyRates,defRates,recoveryRate,recoveryLag) <- buildAssumptionPpyDefRecRate (begDate:cfDates) pAssump + (ppyRates,defRates,recoveryRate,recoveryLag) <- buildAssumptionPpyDefRecRate m (begDate:cfDates) pAssump let txns = projectScheduleFlow [] 1.0 begBal flows defRates ppyRates (replicate curveDatesLength 0.0) (replicate curveDatesLength 0.0) @@ -598,13 +599,13 @@ instance Ast.Asset Mortgage where return $ (applyHaircut ams (CF.CashFlowFrame (begBalAfterCutoff,asOfDay,Nothing) futureTxns) ,historyM) -- schedule mortgage flow WITH delinq - projCashflow (ScheduleMortgageFlow begDate flows dp) asOfDay assumps@(pAssump@(A.MortgageDeqAssump _ _ _ ams),dAssump,fAssump) mRates + projCashflow smf@(ScheduleMortgageFlow begDate flows dp) asOfDay assumps@(pAssump@(A.MortgageDeqAssump _ _ _ ams),dAssump,fAssump) mRates = let begBal = CF.mflowBegBalance $ head flows -- `debug` ("beg date"++show beg_date) in do - (ppyRates, delinqRates,(defaultPct,defaultLag),recoveryRate,recoveryLag) <- Ast.buildAssumptionPpyDelinqDefRecRate (begDate:getDates flows) pAssump + (ppyRates, delinqRates,(defaultPct,defaultLag),recoveryRate,recoveryLag) <- Ast.buildAssumptionPpyDelinqDefRecRate smf (begDate:getDates flows) pAssump let curveDatesLength = defaultLag + recoveryLag + length flows -- `debug` ("Length of rates"++show (length delinqRates)++">>"++show (length ppyRates)) let extraPeriods = defaultLag + recoveryLag -- `debug` ("lags "++show defaultLag++">>"++show recoveryLag) let endDate = CF.getDate (last flows) diff --git a/src/AssetClass/ProjectedCashFlow.hs b/src/AssetClass/ProjectedCashFlow.hs index 2bb31c08..9855545b 100644 --- a/src/AssetClass/ProjectedCashFlow.hs +++ b/src/AssetClass/ProjectedCashFlow.hs @@ -30,10 +30,11 @@ import qualified Cashflow as CF import AssetClass.AssetBase import AssetClass.AssetCashflow -import Cashflow (extendTxns,TsRow(..),mflowBalance) +import Cashflow (extendTxns,TsRow(..)) import Debug.Trace - +import Control.Lens hiding (element,Index) +import Control.Lens.TH debug = flip trace @@ -71,37 +72,31 @@ projectScheduleFlow trs b_factor lastBal [] _ _ (r:rs) (l:ls) (recovery_lag,reco -projFixCfwithAssumption :: (CF.CashFlowFrame, DatePattern) -> Maybe A.AssetPerfAssumption -> Date -> Either String CF.CashFlowFrame +-- ^ project cashflow with floater rate portion +projFixCfwithAssumption :: (CF.CashFlowFrame, DatePattern) -> ([Rate],[Rate],Rate,Int) -> Date -> Either String CF.CashFlowFrame projFixCfwithAssumption (cf@(CF.CashFlowFrame (begBal, begDate, accInt) flows), dp) - mPassump + (ppyRates,defRates,recoveryRate,recoveryLag) asOfDay = let curveDatesLength = recoveryLag + length flows endDate = CF.getDate (last flows) extraDates = genSerialDates dp Exc endDate recoveryLag - (recoveryRate,recoveryLag) = case mPassump of - Nothing -> (0,0) - (Just (A.MortgageAssump _ _ x _) )-> Ast.getRecoveryLagAndRate x - (Just (A.MortgageDeqAssump _ _ x _) )-> Ast.getRecoveryLagAndRate x cfDates = (CF.getDate <$> flows) ++ extraDates in do - (ppyRates,defRates,recoveryRate,recoveryLag) <- case mPassump of - Just pAssump -> buildAssumptionPpyDefRecRate (begDate:cfDates) pAssump - Nothing -> Right (replicate curveDatesLength 0.0, replicate curveDatesLength 0.0, 0.0, 0) - let txns = projectScheduleFlow [] 1.0 begBal flows defRates ppyRates - (replicate curveDatesLength 0.0) - (replicate curveDatesLength 0.0) - (recoveryLag,recoveryRate) -- `debug` (" begin bal"++ show begBal) + (replicate curveDatesLength 0.0) + (replicate curveDatesLength 0.0) + (recoveryLag,recoveryRate) -- `debug` (" begin bal"++ show begBal) let (futureTxns,historyM) = CF.cutoffTrs asOfDay txns let cb = (CF.mflowBegBalance . head) futureTxns return $ CF.CashFlowFrame (cb,asOfDay,Nothing) futureTxns -projIndexCashflows :: ([Date],[Balance],[Principal],Index,Spread) -> DatePattern -> Maybe A.AssetPerfAssumption -> Maybe [RateAssumption] -> Either String CF.CashFlowFrame -projIndexCashflows (ds,bals,principals,index,spd) dp mPassump (Just ras) = +-- ^ project cashflow with fix rate portion +projIndexCashflows :: ([Date],[Balance],[Principal],Index,Spread) -> DatePattern -> ([Rate],[Rate],Rate,Int) -> Maybe [RateAssumption] -> Either String CF.CashFlowFrame +projIndexCashflows (ds,bals,principals,index,spd) dp pAssump (Just ras) = do -- mIndexToApply = A.getRateAssumption ras index indexRates <- sequenceA $ A.lookupRate0 ras index <$> ds @@ -123,42 +118,55 @@ projIndexCashflows (ds,bals,principals,index,spd) dp mPassump (Just ras) = (replicate flowSize Nothing) (replicate flowSize Nothing) (replicate flowSize Nothing) - projFixCfwithAssumption (scheduleCf, dp) mPassump (head ds) + projFixCfwithAssumption (scheduleCf, dp) pAssump (head ds) -- ^ project cashflow with fix rate portion and floater rate portion seperateCashflows :: ProjectedCashflow -> Maybe A.AssetPerfAssumption -> Maybe [RateAssumption] -> Either String (CF.CashFlowFrame, [CF.CashFlowFrame]) -seperateCashflows (ProjectedFlowMixFloater pflow@(CF.CashFlowFrame (begBal, begDate, accuredInt) flows) dp (fixPct,fixRate) floaterList) +seperateCashflows a@(ProjectedFlowMixFloater pflow@(CF.CashFlowFrame (begBal, begDate, accuredInt) flows) dp (fixPct,fixRate) floaterList) mPassump mRates = let begBal = CF.mflowBegBalance $ head flows - totalBals = begBal: (CF.mflowBalance <$> flows) - ds = CF.mflowDate <$> flows + totalBals = begBal: ((view CF.tsRowBalance) <$> flows) + ds = (view CF.tsDate) <$> flows flowSize = length ds -- fix rate cashflow + -- fix balance = total balance * fix percent fixedBals = flip mulBR fixPct <$> totalBals + -- fix principal flow = total principal flow * fix percent fixedPrincipalFlow = flip mulBR fixPct <$> CF.mflowPrincipal <$> flows + -- fix principal interest = total principal flow * fix rate fixedInterestFlow = flip mulBIR fixRate <$> fixedBals fixFlow = zipWith12 MortgageFlow ds 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) - -- float rate cashflow + -- float rate cashflow + -- float balance = total balance - fixed balance totalFloatBalFlow = zipWith (-) totalBals fixedBals + -- float principal flow = total principal flow - fixed principal flow floatPrincipalFlow = zipWith (-) (CF.mflowPrincipal <$> flows) fixedPrincipalFlow - floaterSize = length rs rs = (\(a,b,c) -> a) <$> floaterList -- portion of each floater spds = (\(a,b,c) -> b) <$> floaterList -- spreads indexes = (\(a,b,c) -> c) <$> floaterList -- indexes - + floaterSize = length rs + -- float bal brekdown by index floatBalsBreakDown = (\r -> flip mulBR r <$> totalFloatBalFlow ) <$> rs + -- float principal flow breakdown by index floatPrincipalFlowBreakDown = (\r -> flip mulBR r <$> floatPrincipalFlow) <$> rs -- `debug` ("float bal breakdown"++ show floatBalsBreakDown) + recoveryLag = case mPassump of + Nothing -> 0 + Just passump -> fromMaybe 0 $ getRecoveryLagFromAssumption passump + curveDatesLength = length flows + recoveryLag in - do + do + assumptionInput <- case mPassump of + Just pAssump -> buildAssumptionPpyDefRecRate a (begDate:ds) pAssump + Nothing -> Right (replicate curveDatesLength 0.0, replicate curveDatesLength 0.0, 0.0, 0) fixedCashFlow <- projFixCfwithAssumption ((CF.CashFlowFrame ( ((flip mulBR) fixPct) begBal , begDate , (flip mulBR) fixPct <$> accuredInt) fixFlow) - , dp) mPassump begDate - floatedCashFlow <- sequenceA $ (\x -> projIndexCashflows x dp mPassump mRates) <$> zip5 + , dp) assumptionInput begDate + floatedCashFlow <- sequenceA $ (\x -> projIndexCashflows x dp assumptionInput mRates) <$> zip5 (replicate floaterSize ds) floatBalsBreakDown floatPrincipalFlowBreakDown @@ -188,11 +196,13 @@ instance Ast.Asset ProjectedCashflow where = do (fixedCashFlow, floatedCashFlow) <- seperateCashflows f Nothing mRate -- `debug` ("running fixed cashflow"++show fixedCashFlow) return $ foldl CF.combine fixedCashFlow floatedCashFlow --- projFixCfwithAssumption :: (CF.CashFlowFrame, DatePattern) -> A.AssetPerfAssumption -> Date -> CF.CashFlowFrame + projCashflow f@(ProjectedFlowFixed cf dp) asOfDay (pAssump,_,_) mRates - = - do - p <- projFixCfwithAssumption (cf, dp) (Just pAssump) asOfDay + = do + let cfDates = CF.getDatesCashFlowFrame cf + let begDate = view (CF.cfBeginStatus . _2) cf + pRates <- buildAssumptionPpyDefRecRate f (begDate:cfDates) pAssump + p <- projFixCfwithAssumption (cf, dp) pRates asOfDay return (p, Map.empty) projCashflow f asOfDay (pAssump, _, _) mRates diff --git a/src/AssetClass/Receivable.hs b/src/AssetClass/Receivable.hs index 3bb361c4..a9c51472 100644 --- a/src/AssetClass/Receivable.hs +++ b/src/AssetClass/Receivable.hs @@ -148,7 +148,7 @@ instance Asset Receivable where initTxn = CF.ReceivableFlow sd ob 0 0 0 0 0 0 Nothing in do - defaultRates <- A.buildDefaultRates (sd:[dd]) amd + defaultRates <- A.buildDefaultRates r (sd:[dd]) amd let defaultAmt = mulBR ob (head defaultRates) let afterDefaultBal = ob - defaultAmt let afterDefaultFee = mulBR feeDue (1 - (head defaultRates)) diff --git a/src/Assumptions.hs b/src/Assumptions.hs index 508e59dc..cee93f0a 100644 --- a/src/Assumptions.hs +++ b/src/Assumptions.hs @@ -2,6 +2,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TupleSections #-} module Assumptions (BondPricingInput(..) ,AssumptionInput(..),ApplyAssumptionType(..) @@ -19,6 +20,7 @@ module Assumptions (BondPricingInput(..) ,FieldMatchRule(..),CallOpt(..) ,_MortgageAssump,_MortgageDeqAssump,_LeaseAssump,_LoanAssump,_InstallmentAssump ,_ReceivableAssump,_FixedAssetAssump + ,stressDefaultAssump,applyAssumptionTypeAssetPerf ) where @@ -85,6 +87,23 @@ data ApplyAssumptionType = PoolLevel AssetPerf -- ^ assumption app | ByDealName (Map.Map DealName (ApplyAssumptionType, NonPerfAssumption)) -- ^ assumption for a named deal deriving (Show, Generic) + +applyAssumptionTypeAssetPerf :: Traversal' ApplyAssumptionType AssetPerf +applyAssumptionTypeAssetPerf f = go + where + go (PoolLevel x) = PoolLevel <$> f x + go (ByIndex strats) = ByIndex <$> traverse (\(idxs,aps) -> (idxs,) <$> f aps) strats + go (ByName m) = ByName <$> traverse f m + go (ByObligor os) = ByObligor <$> traverse (\case + ObligorById ids ap -> ObligorById ids <$> f ap + ObligorByTag tags m ap -> ObligorByTag tags m <$> f ap + ObligorByField fs ap -> ObligorByField fs <$> f ap + ObligorByDefault ap -> ObligorByDefault <$> f ap + ) os + go (ByPoolId m) = ByPoolId <$> traverse go m + go (ByDealName m) = ByDealName <$> traverse (\(a,b) -> (,) <$> go a <*> pure b) m + + type RateFormula = DealStats type BalanceFormula = DealStats @@ -133,14 +152,29 @@ data AssetDefaultAssumption = DefaultConstant Rate -- ^ using const | DefaultAtEnd -- ^ default 100% at end | DefaultAtEndByRate Rate Rate -- ^ life time default rate and default rate at end | DefaultStressByTs Ts AssetDefaultAssumption + | DefaultByTerm [[Rate]] deriving (Show,Generic,Read) +-- ^ stress the default assumption by a factor +stressDefaultAssump :: Rate -> AssetDefaultAssumption -> AssetDefaultAssumption +stressDefaultAssump x (DefaultConstant r) = DefaultConstant (r*x) +stressDefaultAssump x (DefaultCDR r) = DefaultCDR (r*x) +stressDefaultAssump x (DefaultVec rs) = DefaultVec ((x*) <$> rs) +stressDefaultAssump x (DefaultVecPadding rs) = DefaultVecPadding ((x*) <$> rs) +stressDefaultAssump x (DefaultByAmt (b,rs)) = DefaultByAmt (mulBR b x, rs) +stressDefaultAssump x (DefaultAtEndByRate r1 r2) = DefaultAtEndByRate (r1*x) (r2*x) +stressDefaultAssump x (DefaultByTerm rss) = DefaultByTerm (map (map (* x)) rss) +stressDefaultAssump x (DefaultStressByTs ts a) = DefaultStressByTs ts (stressDefaultAssump x a) + + data AssetPrepayAssumption = PrepaymentConstant Rate | PrepaymentCPR Rate | PrepaymentVec [Rate] | PrepaymentVecPadding [Rate] | PrepayByAmt (Balance,[Rate]) | PrepayStressByTs Ts AssetPrepayAssumption + | PrepaymentPSA Rate + | PrepaymentByTerm [[Rate]] deriving (Show,Generic,Read) data AssetDelinquencyAssumption = DelinqCDR Rate (Lag,Rate) -- ^ Annualized Rate to Delinq status , period lag become defaulted, loss rate, period lag become loss @@ -191,7 +225,7 @@ data RevolvingAssumption = AvailableAssets RevolvingPool ApplyAssumptionType deriving (Show,Generic) type HistoryCash = Ts -type CurrentHolding = Balance +type CurrentHolding = Balance -- as of the deal date type PricingDate = Date data BondPricingInput = DiscountCurve PricingDate Ts -- ^ PV curve used to discount bond cashflow and a PV date where cashflow discounted to @@ -245,7 +279,6 @@ projRates sr (Floater _ idx spd r dp rfloor rcap mr) (Just assumps) ds ratesFromCurve = case _rateAssumption of (RateCurve _ ts) -> (\x -> spd + (fromRational x) ) <$> (getValByDates ts Inc resetDates) (RateFlat _ v) -> (spd +) <$> replicate (length resetDates) v - _ -> error ("Invalid rate type "++ show _rateAssumption) ratesUsedByDates = getValByDates (mkRateTs $ zip ((head ds):resetDates) (sr:ratesFromCurve)) Inc @@ -256,6 +289,7 @@ projRates sr (Floater _ idx spd r dp rfloor rcap mr) (Just assumps) ds (Just fv, Just cv) -> capWith cv $ floorWith fv $ fromRational <$> ratesUsedByDates (Just fv, Nothing) -> floorWith fv $ fromRational <$> ratesUsedByDates (Nothing, Just cv) -> capWith cv $ fromRational <$> ratesUsedByDates + projRates _ rt rassump ds = Left ("Invalid rate type: "++ show rt++" assump: "++ show rassump) @@ -267,6 +301,8 @@ calcResetDates (r:rs) bs | r == head rs = calcResetDates rs (bs++[False]) | otherwise = calcResetDates rs (bs++[True]) +makePrisms ''AssetPerfAssumption +makePrisms ''AssetDefaultAssumption $(deriveJSON defaultOptions ''CallOpt) $(deriveJSON defaultOptions ''BondPricingInput) @@ -279,5 +315,3 @@ $(concat <$> traverse (deriveJSON defaultOptions) [''FieldMatchRule,''TagMatchRu , ''AssetDefaultedPerfAssumption, ''AssetDelinqPerfAssumption, ''NonPerfAssumption, ''AssetDefaultAssumption , ''AssetPrepayAssumption, ''RecoveryAssumption, ''ExtraStress , ''LeaseAssetGapAssump, ''LeaseAssetRentAssump, ''RevolvingAssumption, ''AssetDelinquencyAssumption,''InspectType]) - -makePrisms ''AssetPerfAssumption \ No newline at end of file diff --git a/src/Cashflow.hs b/src/Cashflow.hs index fa563608..ff2310d9 100644 --- a/src/Cashflow.hs +++ b/src/Cashflow.hs @@ -1,26 +1,27 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveAnyClass #-} module Cashflow (CashFlowFrame(..),Principals,Interests,Amount - ,combine,mergePoolCf,sumTsCF,tsSetDate,tsSetLoss,tsSetRecovery + ,combine,mergePoolCf,sumTsCF,tsSetLoss,tsSetRecovery ,sizeCashFlowFrame,aggTsByDates ,mflowInterest,mflowPrincipal,mflowRecovery,mflowPrepayment ,mflowRental,mflowRate,sumPoolFlow,splitTrs,aggregateTsByDate - ,mflowDefault,mflowLoss,mflowDate + ,mflowDefault,mflowLoss ,getSingleTsCashFlowFrame,getDatesCashFlowFrame ,lookupSource,lookupSourceM,combineTss - ,mflowBalance,mflowBegBalance,tsDefaultBal - ,mflowBorrowerNum,mflowPrepaymentPenalty + ,mflowBegBalance,tsDefaultBal + ,mflowBorrowerNum,mflowPrepaymentPenalty,tsRowBalance ,emptyTsRow,mflowAmortAmount ,tsTotalCash, setPrepaymentPenalty, setPrepaymentPenaltyFlow ,getDate,getTxnLatestAsOf,totalPrincipal - ,mflowWeightAverageBalance - ,addFlowBalance,totalLoss,totalDefault,totalRecovery,firstDate + ,mflowWeightAverageBalance,tsDate + ,totalLoss,totalDefault,totalRecovery,firstDate ,shiftCfToStartDate,cfInsertHead,buildBegTsRow,insertBegTsRow ,tsCumDefaultBal,tsCumDelinqBal,tsCumLossBal,tsCumRecoveriesBal ,TsRow(..),cfAt,cutoffTrs,patchCumulative,extendTxns,dropTailEmptyTxns ,cashflowTxn,clawbackInt,scaleTsRow,mflowFeePaid, currentCumulativeStat, patchCumulativeAtInit - ,mergeCf,buildStartTsRow, sliceCfFrame + ,mergeCf,buildStartTsRow ,txnCumulativeStats,consolidateCashFlow, cfBeginStatus, getBegBalCashFlowFrame ,splitCashFlowFrameByDate, mergePoolCf2, buildBegBal, extendCashFlow, patchBalance) where @@ -47,6 +48,7 @@ import Debug.Trace import qualified Control.Lens as Map import Control.Applicative (liftA2) import Data.OpenApi (HasPatch(patch), HasXml (xml)) +import Control.DeepSeq (NFData,rnf) import Data.Text.Internal.Encoding.Fusion (streamUtf16BE) import qualified Text.Tabular as TT @@ -103,7 +105,7 @@ data TsRow = CashFlow Date Amount | FixedFlow Date Balance NewDepreciation Depreciation Balance Balance -- unit cash | ReceivableFlow Date Balance AccuredFee Principal FeePaid Default Recovery Loss (Maybe CumulativeStat) -- remain balance, amortized amount, unit, cash - deriving(Show,Eq,Ord,Generic) + deriving(Show,Eq,Ord,Generic,NFData) instance Semigroup TsRow where CashFlow d1 a1 <> (CashFlow d2 a2) = CashFlow (max d1 d2) (a1 + a2) @@ -217,6 +219,9 @@ instance Show CashFlowFrame where in show st <> "\n" <> A.render id id id tbl +instance NFData CashFlowFrame where + rnf (CashFlowFrame st txns) = rnf st `seq` rnf txns + rnf (MultiCashFlowFrame m) = rnf m sizeCashFlowFrame :: CashFlowFrame -> Int sizeCashFlowFrame (CashFlowFrame _ ts) = length ts @@ -326,7 +331,7 @@ combineTss [] r [] = r combineTss [] (r1:r1s) (r2:r2s) | getDate r1 > getDate r2 = combineTss [] (r2:r2s) (r1:r1s) | getDate r1 == getDate r2 = combineTss [combineTs r1 r2] r1s r2s -- `debug` ("combineTss after same"++show r1s++" "++show r2s) - | otherwise = combineTss [updateFlowBalance (mflowBegBalance r2+mflowBalance r1) r1] + | otherwise = combineTss [set tsRowBalance (mflowBegBalance r2+(view tsRowBalance r1)) r1] r1s (r2:r2s) @@ -346,17 +351,17 @@ appendTs :: TsRow -> TsRow -> TsRow appendTs bn1@(BondFlow d1 b1 _ _ ) bn2@(BondFlow d2 b2 p2 i2 ) = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 -- `debug` ("b1 >> "++show b1++">>"++show (mflowAmortAmount bn2)) appendTs (MortgageDelinqFlow d1 b1 p1 i1 prep1 _ def1 rec1 los1 rat1 mbn1 _ mstat1) bn2@(MortgageDelinqFlow _ b2 p2 i2 prep2 _ def2 rec2 los2 rat2 mbn2 _ mstat2) - = updateFlowBalance (b1 - mflowAmortAmount bn2) bn2 + = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 appendTs bn1@(MortgageFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mbn1 _ mstat1) bn2@(MortgageFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 mbn2 _ mstat2) - = updateFlowBalance (b1 - mflowAmortAmount bn2) bn2 -- `debug` ("Summing stats"++ show bn1 ++ show mstat1++">>"++ show bn2 ++ show mstat2) + = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 -- `debug` ("Summing stats"++ show bn1 ++ show mstat1++">>"++ show bn2 ++ show mstat2) appendTs (LoanFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mstat1) bn2@(LoanFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 mstat2) - = updateFlowBalance (b1 - mflowAmortAmount bn2) bn2 + = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 appendTs (LeaseFlow d1 b1 r1) bn2@(LeaseFlow d2 b2 r2) - = updateFlowBalance (b1 - mflowAmortAmount bn2) bn2 + = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 appendTs (FixedFlow d1 b1 de1 cde1 p1 c1 ) bn2@(FixedFlow d2 b2 de2 cde2 p2 c2) - = updateFlowBalance (b1 - mflowAmortAmount bn2) bn2 + = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 appendTs (ReceivableFlow d1 b1 af1 p1 fp1 def1 rec1 los1 mstat1) bn2@(ReceivableFlow _ b2 af2 p2 fp2 def2 rec2 los2 mstat2) - = updateFlowBalance (b1 - mflowAmortAmount bn2) bn2 + = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 appendTs _1 _2 = error $ "appendTs failed with "++ show _1 ++ ">>" ++ show _2 -- ^ add up TsRow from same entity @@ -393,13 +398,13 @@ buildBegBal (x:_) = mflowBegBalance x sumTs :: [TsRow] -> Date -> TsRow -sumTs trs = tsSetDate (foldr1 addTs trs) +sumTs trs d = set tsDate d (foldr1 addTs trs) -- ^ group cashflow from same entity by a single date sumTsCF :: [TsRow] -> Date -> TsRow -- sumTsCF [] = tsSetDate (foldl1 addTsCF trs) -- `debug` ("Summing"++show trs++">>"++ show (tsSetDate (foldr1 addTsCF trs) d)) -sumTsCF [] = error "sumTsCF failed with empty list" -sumTsCF trs = tsSetDate (foldl1 addTsCF trs) -- `debug` ("Summing"++show trs++">>"++ show (tsSetDate (foldr1 addTsCF trs) d)) +sumTsCF [] _ = error "sumTsCF failed with empty list" +sumTsCF trs d = set tsDate d (foldl1 addTsCF trs) -- `debug` ("Summing"++show trs++">>"++ show (tsSetDate (foldr1 addTsCF trs) d)) tsTotalCash :: TsRow -> Balance tsTotalCash (CashFlow _ x) = x @@ -411,15 +416,15 @@ tsTotalCash (LeaseFlow _ _ a) = a tsTotalCash (FixedFlow _ _ _ _ _ x) = x tsTotalCash (ReceivableFlow _ _ _ a b _ c _ _ ) = a + b + c -tsDefaultBal :: TsRow -> Balance -tsDefaultBal CashFlow {} = error "not supported" -tsDefaultBal BondFlow {} = error "not supported" -tsDefaultBal (MortgageDelinqFlow _ _ _ _ _ _ x _ _ _ _ _ _) = x -tsDefaultBal (MortgageFlow _ _ _ _ _ x _ _ _ _ _ _) = x -tsDefaultBal (LoanFlow _ _ _ _ _ x _ _ _ _) = x -tsDefaultBal LeaseFlow {} = error "not supported" -tsDefaultBal (FixedFlow _ _ x _ _ _) = x -tsDefaultBal (ReceivableFlow _ _ _ _ _ x _ _ _ ) = x +tsDefaultBal :: TsRow -> Either String Balance +tsDefaultBal CashFlow {} = Left "no default amount for bond flow" +tsDefaultBal BondFlow {} = Left "no default amount for bond flow" +tsDefaultBal (MortgageDelinqFlow _ _ _ _ _ _ x _ _ _ _ _ _) = Right x +tsDefaultBal (MortgageFlow _ _ _ _ _ x _ _ _ _ _ _) = Right x +tsDefaultBal (LoanFlow _ _ _ _ _ x _ _ _ _) = Right x +tsDefaultBal LeaseFlow {} = Left "not default amoutn for lease flow" +tsDefaultBal (FixedFlow _ _ x _ _ _) = Right x +tsDefaultBal (ReceivableFlow _ _ _ _ _ x _ _ _ ) = Right x tsCumulative :: Lens' TsRow (Maybe CumulativeStat) tsCumulative = lens getter setter @@ -448,16 +453,6 @@ tsCumLossBal tr = preview (tsCumulative . _Just . _6) tr tsCumRecoveriesBal :: TsRow -> Maybe Balance tsCumRecoveriesBal tr = preview (tsCumulative . _Just . _5) tr -tsSetDate :: TsRow -> Date -> TsRow -tsSetDate (CashFlow _ a) x = CashFlow x a -tsSetDate (BondFlow _ a b c) x = BondFlow x a b c -tsSetDate (MortgageDelinqFlow _ a b c d e f g h i j k l) x = MortgageDelinqFlow x a b c d e f g h i j k l -tsSetDate (MortgageFlow _ a b c d e f g h i j k) x = MortgageFlow x a b c d e f g h i j k -tsSetDate (LoanFlow _ a b c d e f g h i) x = LoanFlow x a b c d e f g h i -tsSetDate (LeaseFlow _ a b) x = LeaseFlow x a b -tsSetDate (FixedFlow _ a b c d e) x = FixedFlow x a b c d e -tsSetDate (ReceivableFlow _ a b c d e f g h) x = ReceivableFlow x a b c d e f g h - tsDate :: Lens' TsRow Date tsDate = lens getter setter where @@ -478,17 +473,6 @@ tsDate = lens getter setter setter (FixedFlow _ a b c d e) x = FixedFlow x a b c d e setter (ReceivableFlow _ a b c d e f g h) x = ReceivableFlow x a b c d e f g h - -tsSetBalance :: Balance -> TsRow -> TsRow -tsSetBalance x (CashFlow _d a) = CashFlow _d x -tsSetBalance x (BondFlow _d a b c) = BondFlow _d x b c -tsSetBalance x (MortgageDelinqFlow _d a b c d e f g h i j k l) = MortgageDelinqFlow _d x b c d e f g h i j k l -tsSetBalance x (MortgageFlow _d a b c d e f g h i j k) = MortgageFlow _d x b c d e f g h i j k -tsSetBalance x (LoanFlow _d a b c d e f g h i) = LoanFlow _d x b c d e f g h i -tsSetBalance x (LeaseFlow _d a b) = LeaseFlow _d x b -tsSetBalance x (FixedFlow _d a b c d e) = FixedFlow _d x b c d e -tsSetBalance x (ReceivableFlow _d a b c d e f g h) = ReceivableFlow _d x b c d e f g h - tsSetLoss :: Balance -> TsRow -> TsRow tsSetLoss x (MortgageDelinqFlow _d a b c d e f g h i j k l) = MortgageDelinqFlow _d a b c d e f g x i j k l tsSetLoss x (MortgageFlow _d a b c d e f g h i j k) = MortgageFlow _d a b c d e f x h i j k @@ -613,7 +597,6 @@ mflowRecovery FixedFlow {} = 0 mflowRecovery (ReceivableFlow _ _ _ _ _ _ x _ _ ) = x mflowRecovery _ = error "not supported" - tsRowBalance :: Lens' TsRow Balance tsRowBalance = lens getter setter where @@ -634,34 +617,6 @@ tsRowBalance = lens getter setter setter (ReceivableFlow a _ b c d e f g h) x = ReceivableFlow a x b c d e f g h -mflowBalance :: TsRow -> Balance -mflowBalance (BondFlow _ x _ _) = x -mflowBalance (MortgageFlow _ x _ _ _ _ _ _ _ _ _ _) = x -mflowBalance (MortgageDelinqFlow _ x _ _ _ _ _ _ _ _ _ _ _) = x -mflowBalance (LoanFlow _ x _ _ _ _ _ _ _ _) = x -mflowBalance (LeaseFlow _ x _ ) = x -mflowBalance (FixedFlow _ x _ _ _ _) = x -mflowBalance (ReceivableFlow _ x _ _ _ _ _ _ _ ) = x - -addFlowBalance :: Balance -> TsRow -> TsRow -addFlowBalance 0 x = x -addFlowBalance b (MortgageFlow a x c d e f g h i j k l) = MortgageFlow a (x+b) c d e f g h i j k l -addFlowBalance b (MortgageDelinqFlow a x c d e f g h i j k l m) = MortgageDelinqFlow a (x+b) c d e f g h i j k l m -addFlowBalance b (LoanFlow a x c d e f g i j k) = LoanFlow a (x+b) c d e f g i j k -addFlowBalance b (LeaseFlow a x c ) = LeaseFlow a (x+b) c -addFlowBalance b (FixedFlow a x c d e f ) = FixedFlow a (x+b) c d e f -addFlowBalance b (ReceivableFlow a x c d e f g h i) = ReceivableFlow a (x+b) c d e f g h i - -updateFlowBalance :: Balance -> TsRow -> TsRow -updateFlowBalance b (BondFlow x _ p i) = BondFlow x b p i -updateFlowBalance b (MortgageDelinqFlow a x c d e f g h i j k l m ) = MortgageDelinqFlow a b c d e f g h i j k l m -updateFlowBalance b (MortgageFlow a x c d e f g h i j k l) = MortgageFlow a b c d e f g h i j k l -updateFlowBalance b (LoanFlow a x c d e f g i j k) = LoanFlow a b c d e f g i j k -updateFlowBalance b (LeaseFlow a x c ) = LeaseFlow a b c -updateFlowBalance b (FixedFlow a x c d e f ) = FixedFlow a b c d e f -updateFlowBalance b (ReceivableFlow a x c d e f g h i) = ReceivableFlow a b c d e f g h i - - mflowBegBalance :: TsRow -> Balance mflowBegBalance (BondFlow _ x p _) = x + p mflowBegBalance (MortgageDelinqFlow _ x p _ ppy delinq def _ _ _ _ _ _) = x + p + ppy + delinq @@ -698,15 +653,6 @@ mflowFeePaid :: TsRow -> Amount mflowFeePaid (ReceivableFlow _ _ _ _ x _ _ _ _ ) = x mflowFeePaid _ = 0 - -mflowDate :: TsRow -> Date --- ^ get date for a cashflow record -mflowDate (MortgageFlow x _ _ _ _ _ _ _ _ _ _ _) = x -mflowDate (MortgageDelinqFlow x _ _ _ _ _ _ _ _ _ _ _ _) = x -mflowDate (LoanFlow x _ _ _ _ _ _ _ _ _) = x -mflowDate (LeaseFlow x _ _ ) = x -mflowDate (ReceivableFlow x _ _ _ _ _ _ _ _ ) = x - mflowAmortAmount :: TsRow -> Balance -- ^ calculate amortized amount for cashflow (for defaults only) mflowAmortAmount (MortgageFlow _ _ p _ ppy def _ _ _ _ _ _) = p + ppy + def @@ -736,8 +682,8 @@ mflowWeightAverageBalance :: Date -> Date -> [TsRow] -> Balance mflowWeightAverageBalance sd ed trs = sum $ zipWith mulBR _bals _dfs -- `debug` ("CalcingAvgBal=>"++show sd++show ed++show txns ) where - txns = filter (\x -> (mflowDate x>=sd)&& mflowDate x<=ed) trs - _ds = map mflowDate txns -- `debug` ("fee base txns"++show txns) + txns = filter (\x -> (view tsDate x >=sd)&& (view tsDate x)<=ed) trs + _ds = view tsDate <$> txns -- `debug` ("fee base txns"++show txns) _bals = map mflowBegBalance txns _dfs = getIntervalFactors $ sd:_ds @@ -776,7 +722,7 @@ buildBegTsRow :: Date -> TsRow -> TsRow buildBegTsRow d flow@FixedFlow{} = flow buildBegTsRow d tr = let - r = tsSetBalance (mflowBalance tr + mflowAmortAmount tr) (emptyTsRow d tr) + r = set tsRowBalance ((view tsRowBalance tr) + mflowAmortAmount tr) (emptyTsRow d tr) rate = mflowRate tr in tsSetRate rate r @@ -786,7 +732,7 @@ buildStartTsRow (CashFlowFrame (begBal,begDate,accInt) []) = Nothing buildStartTsRow (CashFlowFrame (begBal,begDate,accInt) (txn:txns)) = let rEmpty = emptyTsRow begDate txn - r = tsSetBalance begBal rEmpty + r = set tsRowBalance begBal rEmpty rate = mflowRate txn in Just $ tsSetRate rate r @@ -838,19 +784,6 @@ mergePoolCf cf1@(CashFlowFrame st1 txns1) cf2@(CashFlowFrame st2 txns2) CashFlowFrame st1 (txn0++txn1) -- `debug` ("Txn1"++show txn1) where [startDate1,startDate2] = firstDate <$> [cf1,cf2] - -- rightToLeft = startDate1 >= startDate2 - -sliceCfFrame :: Date -> Date -> RangeType -> CashFlowFrame -> CashFlowFrame -sliceCfFrame sd ed rt (CashFlowFrame st txns) - = let - txns' = case rt of - EE -> filter (\x -> (mflowDate x > sd) && (mflowDate x < ed)) txns - EI -> filter (\x -> (mflowDate x > sd) && (mflowDate x <= ed)) txns - II -> filter (\x -> (mflowDate x >= sd) && (mflowDate x <= ed)) txns - IE -> filter (\x -> (mflowDate x >= sd) && (mflowDate x < ed)) txns - in - CashFlowFrame st txns' - -- ^ agg cashflow (but not updating the cumulative stats) @@ -986,7 +919,7 @@ lookupSource tr CollectedCash = tsTotalCash tr lookupSource tr NewDelinquencies = mflowDelinq tr lookupSource tr NewDefaults = mflowDefault tr lookupSource tr NewLosses = mflowLoss tr -lookupSource tr CurBalance = mflowBalance tr +lookupSource tr CurBalance = view tsRowBalance tr lookupSource tr CurBegBalance = mflowBegBalance tr lookupSource tr x = error ("Failed to lookup source"++ show x) diff --git a/src/DateUtil.hs b/src/DateUtil.hs index 2ff9e93f..6340e1e9 100644 --- a/src/DateUtil.hs +++ b/src/DateUtil.hs @@ -24,7 +24,8 @@ import Control.Exception debug = flip trace -- http://www.deltaquants.com/day-count-conventions -yearCountFraction :: DayCount -> Date -> Date -> Rational --TODO https://www.iso20022.org/15022/uhb/mt565-16-field-22f.htm +-- https://www.iso20022.org/15022/uhb/mt565-16-field-22f.htm +yearCountFraction :: DayCount -> Date -> Date -> Rational yearCountFraction dc sd ed = case dc of DC_ACT_ACT -> if sameYear then diff --git a/src/Deal.hs b/src/Deal.hs index bcb6b47d..0a82e467 100644 --- a/src/Deal.hs +++ b/src/Deal.hs @@ -4,6 +4,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE TupleSections #-} module Deal (run,runPool,getInits,runDeal,ExpectReturn(..) ,performAction @@ -11,10 +12,11 @@ module Deal (run,runPool,getInits,runDeal,ExpectReturn(..) ,calcTargetAmount,updateLiqProvider ,projAssetUnion,priceAssetUnion ,removePoolCf,runPoolType,PoolType - ,ActionOnDate(..),DateDesp(..),OverrideType(..) + ,ActionOnDate(..),DateDesp(..) ,changeDealStatus ) where +import Control.Parallel.Strategies import qualified Accounts as A import qualified Ledger as LD import qualified Asset as Ast @@ -31,7 +33,6 @@ import AssetClass.Mortgage import AssetClass.Lease import AssetClass.Loan import AssetClass.Installment - import AssetClass.MixedAsset import qualified Call as C @@ -102,10 +103,6 @@ setBondNewRate t d ras b@(L.Bond _ _ _ (L.RefRate sr ds factor _) _ bal currentR rate <- queryCompound t d (patchDateToStats d ds) return b' {L.bndRate = fromRational (rate * toRational factor) } --- ^ do nothing for bond with interest by yield -setBondNewRate t d ras b@(L.Bond _ _ _ (L.InterestByYield {}) _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) - = Right b - -- ^ cap & floor & IoI setBondNewRate t d ras b@(L.Bond _ _ _ ii _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) = Right $ (L.accrueInt d b) { L.bndRate = applyFloatRate ii d ras} @@ -183,22 +180,6 @@ updateLiqProviderRate t d ras liq@CE.LiqFacility{CE.liqRateType = mRt, CE.liqPre updateLiqProviderRate t d ras liq = liq --- CDS - --- ^ accure CDS --- accrueCDS :: Ast.Asset a => TestDeal a -> Date -> CE.CreditDefaultSwap -> Either String CE.CreditDefaultSwap --- accrueCDS t d cds@CDS{} = Right cds - --- accrueCDS - - - --- ^ settle CDS --- settleCDS :: Ast.Asset a => TestDeal a -> Date -> CE.CDS -> Either String (CE.CDS - - - - evalFloaterRate :: Date -> [RateAssumption] -> IR.RateType -> IRate evalFloaterRate _ _ (IR.Fix _ r) = r evalFloaterRate d ras (IR.Floater _ idx spd _r _ mFloor mCap mRounding) @@ -313,9 +294,6 @@ accrueRC t d rs rc@RateCap{rcNetCash = amt, rcStrikeRate = strike,rcIndex = inde let newStmt = appendStmt (IrsTxn d newAmt addAmt 0 0 0 SwapAccrue) mstmt return $ rc { rcLastStlDate = Just d ,rcNetCash = newAmt, rcStmt = newStmt } - - - -- ^ test if a clean up call should be fired testCall :: Ast.Asset a => TestDeal a -> Date -> C.CallOption -> Either String Bool testCall t d opt = @@ -391,7 +369,6 @@ changeDealStatus:: Ast.Asset a => (Date,String)-> DealStatus -> TestDeal a -> (M changeDealStatus _ _ t@TestDeal{status=Ended} = (Nothing, t) changeDealStatus (d,why) newSt t@TestDeal{status=oldSt} = (Just (DealStatusChangeTo d oldSt newSt why), t {status=newSt}) - -- runWaterfall :: Ast.Asset a => (TestDeal a ,Date, Runcontext a ,[ResultComponent]) -> String -> Either String (TestDeal a, RunContext a,[ResultComponent]) -- runWaterfall (t,d, runContext,logs) waterfallKey = -- let @@ -436,7 +413,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= -- deposit cashflow to SPV from external pool cf in do - let accs = depositPoolFlow (collects t) d collectedFlow accMap -- `debug` ("PoolCollection: deposit >>"++ show d++">>>"++ show collectedFlow++"\n") + accs <- depositPoolFlow (collects t) d collectedFlow accMap -- `debug` ("PoolCollection: deposit >>"++ show d++">>>"++ show collectedFlow++"\n") let dAfterDeposit = (appendCollectedCF d t collectedFlow) {accounts=accs} -- `debug` ("Collected flow"++ show collectedFlow) -- newScheduleFlowMap = Map.map (over CF.cashflowTxn (cutBy Exc Future d)) (fromMaybe Map.empty (getScheduledCashflow t Nothing)) let dealAfterUpdateScheduleFlow = over dealScheduledCashflow @@ -448,7 +425,13 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= let waterfallToExe = Map.findWithDefault [] W.EndOfPoolCollection (waterfall t) -- `debug` ("new logs from trigger 1"++ show newLogs0) (dAfterAction,rc2,newLogs) <- foldM (performActionWrap d) (dRunWithTrigger0 ,rc1 ,log ) waterfallToExe -- `debug` ("Pt 03"++ show d++">> context flow"++show (pool dRunWithTrigger0))-- `debug` ("End collection action"++ show waterfallToExe) (dRunWithTrigger1,rc3,ads3,newLogs1) <- runTriggers (dAfterAction,rc2,ads2) d EndCollectionWF -- `debug` ("PoolCollection: Pt 04"++ show d++">> context flow"++show (runPoolFlow rc2))-- `debug` ("End collection action"++ show waterfallToExe) - run dRunWithTrigger1 (runPoolFlow rc3) (Just ads3) rates calls rAssump (newLogs0++newLogs++ eopActionsLog ++newLogs1) -- `debug` ("PoolCollection: Pt 05>> "++ show d++">> context flow>> "++show (runPoolFlow rc3)) + run (increasePoolCollectedPeriod dRunWithTrigger1 ) + (runPoolFlow rc3) + (Just ads3) + rates + calls + rAssump + (newLogs0++newLogs++ eopActionsLog ++newLogs1) -- `debug` ("PoolCollection: Pt 05>> "++ show d++">> context flow>> "++show (runPoolFlow rc3)) else run t poolFlowMap (Just ads) rates calls rAssump log -- `debug` ("PoolCollection: hit zero pool length"++ show d++"pool"++ (show poolFlowMap)++"collected cf"++ show pt) @@ -482,9 +465,15 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= do (dAfterWaterfall, rc2, newLogsWaterfall) <- foldM (performActionWrap d) (dRunWithTrigger0,rc1,log) waterfallToExe -- `debug` ("In RunWaterfall Date"++show d++">>> status "++show (status dRunWithTrigger0)++"before run waterfall collected >>"++ show (pool dRunWithTrigger0)) (dRunWithTrigger1, rc3, ads2, newLogs2) <- runTriggers (dAfterWaterfall,rc2,ads1) d EndDistributionWF -- `debug` ("In RunWaterfall Date"++show d++"after run waterfall >>"++ show (runPoolFlow rc2)++" collected >>"++ show (pool dAfterWaterfall)) - run dRunWithTrigger1 (runPoolFlow rc3) (Just ads2) rates calls rAssump (newLogsWaterfall++newLogs2++logsBeforeDist++[RunningWaterfall d waterfallKey]) -- `debug` ("In RunWaterfall Date"++show d++"after run waterfall 3>>"++ show (pool dRunWithTrigger1)++" status>>"++ show (status dRunWithTrigger1)) - - -- Custom waterfall execution action from deal dates + run (increaseBondPaidPeriod dRunWithTrigger1) + (runPoolFlow rc3) + (Just ads2) + rates + calls + rAssump + (newLogsWaterfall++newLogs2++logsBeforeDist++[RunningWaterfall d waterfallKey]) -- `debug` ("In RunWaterfall Date"++show d++"after run waterfall 3>>"++ show (pool dRunWithTrigger1)++" status>>"++ show (status dRunWithTrigger1)) + + -- Custom waterfall execution action from custom dates RunWaterfall d wName -> let runContext = RunContext poolFlowMap rAssump rates @@ -514,7 +503,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newF <- calcDueFee t d fToAcc let newFeeMap = (Map.fromList [(feeName,newF)]) <> feeMap run (t{fees=newFeeMap}) poolFlowMap (Just ads) rates calls rAssump log - + ResetLiqProvider d liqName -> case liqProvider t of Nothing -> run t poolFlowMap (Just ads) rates calls rAssump log @@ -626,14 +615,14 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newBndMap <- adjustM (setBondStepUpRate t d (fromMaybe [] rates)) bn bndMap run t{bonds = newBndMap } poolFlowMap (Just ads) rates calls rAssump log - -- TODO When reset rate, need to accrue interest ResetAccRate d accName -> do newAccMap <- adjustM (\a@(A.Account _ _ (Just (A.InvestmentAccount idx spd dp dp1 lastDay _)) _ _) -> do newRate <- AP.lookupRate (fromMaybe [] rates) (idx,spd) d - return a { A.accInterest = Just (A.InvestmentAccount idx spd dp dp1 lastDay newRate)}) + let accWithNewInt = A.depositInt d a + return accWithNewInt { A.accInterest = Just (A.InvestmentAccount idx spd dp dp1 lastDay newRate)}) accName accMap run t{accounts = newAccMap} poolFlowMap (Just ads) rates calls rAssump log @@ -708,8 +697,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newAcc = Map.adjust (A.deposit fundAmt d (FundWith bName fundAmt)) accName accMap in do - newBnd <- calcDueInt t d Nothing Nothing $ bndMap Map.! bName - let bndFunded = L.fundWith d fundAmt newBnd + let bndFunded = L.fundWith d fundAmt $ bndMap Map.! bName run t{accounts = newAcc, bonds = Map.insert bName bndFunded bndMap} poolFlowMap (Just ads) rates calls rAssump log @@ -723,8 +711,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= False -> run t poolFlowMap (Just ads) rates calls rAssump (log ++ [WarningMsg ("Failed to fund bond"++ bName++ ":" ++show p)]) True -> do - newBnd <- calcDueInt t d Nothing Nothing $ bndMap Map.! bName - let bndFunded = L.fundWith d fundAmt newBnd + let bndFunded = L.fundWith d fundAmt $ bndMap Map.! bName run t{accounts = newAcc, bonds = Map.insert bName bndFunded bndMap} poolFlowMap (Just ads) rates calls rAssump log @@ -840,44 +827,18 @@ data ExpectReturn = DealPoolFlow | ExecutionSummary deriving (Show,Generic) + +-- TODO : need to lift the result and make function Either String xxx priceBonds :: TestDeal a -> AP.BondPricingInput -> Map.Map String L.PriceResult priceBonds t (AP.DiscountCurve d dc) = Map.map (L.priceBond d dc) (viewBondsInMap t) priceBonds t@TestDeal {bonds = bndMap} (AP.RunZSpread curve bondPrices) = Map.mapWithKey (\bn (pd,price)-> L.ZSpread $ - L.calcZspread - (price,pd) - 0 - (1.0 - ,(1.0,0.5) - ,toRational (rateToday pd - toRational (L.bndRate (bndMap Map.!bn)))) - (bndMap Map.! bn) - curve) + case L.calcZspread (price,pd) (bndMap Map.! bn) curve of + Left e -> error e + Right z -> z + ) bondPrices - where - rateToday = getValByDate curve Inc - --- priceBonds t@TestDeal {bonds = bndMap} (AP.IRRInput inputList) --- = let --- bondHistoryFlow = [] --- futureCashFlow = [] --- in - --- priceBondIrr :: BondName -> (HistoryCash,CurrentHolding,Maybe (Dates, PricingMethod)) -> Map.Map BondName L.Bond -> L.PriceResult --- priceBondIrr bName (historyCashflow,position,mSell) m = --- let --- b = m Map.! bName --- bBegBal = L.bndBalance b --- bPct = position / bBegBal --- bProjFlow = (\s -> (getDate s, mulBR (getTxnAmt s) bPct)) <$> getTxns $ L.bndStmt b --- bCashFlow = bProjFlow ++ historyCashflow --- bLastCf Nothing = [] --- bLastCf (Just (ds,ByRate r)) = [] --- bLastCf (Just (ds,ByCurve ts)) = [] --- bLastCf (Just (ds,ByBalanceFactor r)) = [] --- bLastCf (Just (ds,ByDm idx spd)) = [] --- in --- -- ^ split call option assumption , @@ -887,17 +848,17 @@ splitCallOpts :: AP.CallOpt -> ([Pre],[Pre]) splitCallOpts (AP.CallPredicate ps) = (ps,[]) splitCallOpts (AP.LegacyOpts copts) = let - cFn (C.PoolBalance bal) = If L (CurrentPoolBalance Nothing) bal - cFn (C.BondBalance bal) = If L CurrentBondBalance bal - cFn (C.PoolFactor r) = IfRate L (PoolFactor Nothing) (fromRational r) - cFn (C.BondFactor r) = IfRate L BondFactor (fromRational r) - cFn (C.OnDate d) = IfDate E d - cFn (C.AfterDate d) = IfDate G d - cFn (C.And _opts) = All [ cFn o | o <- _opts ] - cFn (C.Or _opts) = Any [ cFn o | o <- _opts ] - cFn (C.Pre p) = p + cFn (C.PoolBalance bal) = If L (CurrentPoolBalance Nothing) bal + cFn (C.BondBalance bal) = If L CurrentBondBalance bal + cFn (C.PoolFactor r) = IfRate L (PoolFactor Nothing) (fromRational r) + cFn (C.BondFactor r) = IfRate L BondFactor (fromRational r) + cFn (C.OnDate d) = IfDate E d + cFn (C.AfterDate d) = IfDate G d + cFn (C.And _opts) = All [ cFn o | o <- _opts ] + cFn (C.Or _opts) = Any [ cFn o | o <- _opts ] + cFn (C.Pre p) = p in - ([ cFn copt | copt <- copts ],[]) + ([ cFn copt | copt <- copts ],[]) -- legacyCallOptConvert (AP.CallOptions opts) = concat [ legacyCallOptConvert o | o <- opts ] splitCallOpts (AP.CallOnDates dp ps) = ([],ps) splitCallOpts x = error $ "Failed to find call option types but got"++ show x @@ -915,11 +876,7 @@ readCallOptions opts = runDeal :: Ast.Asset a => TestDeal a -> ExpectReturn -> Maybe AP.ApplyAssumptionType-> AP.NonPerfAssumption -> Either String (TestDeal a, Maybe (Map.Map PoolId CF.CashFlowFrame), Maybe [ResultComponent], Maybe (Map.Map String L.PriceResult)) -runDeal t _ perfAssumps nonPerfAssumps@AP.NonPerfAssumption{AP.callWhen = opts - ,AP.pricing = mPricing - ,AP.revolving = mRevolving - ,AP.interest = mInterest} - -- | not runFlag = Right $ (t, Nothing, Just valLogs, Nothing) --TODO should be left as warning errors to be sent back to user +runDeal t _ perfAssumps nonPerfAssumps@AP.NonPerfAssumption{AP.callWhen = opts ,AP.pricing = mPricing ,AP.revolving = mRevolving ,AP.interest = mInterest} | not runFlag = Left $ intercalate ";" $ show <$> valLogs | otherwise = do @@ -947,7 +904,6 @@ runDeal t _ perfAssumps nonPerfAssumps@AP.NonPerfAssumption{AP.callWhen = opts -- run() is a recusive function loop over all actions till deal end conditions are met -- | get bond principal and interest shortfalls from a deal --- TODO , what if bonds has funded during life time ? whatÅ› the correct bond beg balance to be used ? getRunResult :: Ast.Asset a => TestDeal a -> [ResultComponent] getRunResult t = os_bn_i ++ os_bn_b -- `debug` ("Done with get result") where @@ -959,14 +915,11 @@ prepareDeal :: Ast.Asset a => TestDeal a -> TestDeal a prepareDeal t@TestDeal {bonds = bndMap, liqProvider = mLiqProvider} = let pIdCf = view dealCashflow t - -- dropTailEmptyTxns newPtMap = Map.map (\mCf -> (over CF.cashflowTxn CF.dropTailEmptyTxns) <$> mCf ) pIdCf t1 = set dealCashflow newPtMap t in - t1 {bonds = Map.map (L.patchBondFactor . L.consolStmt) bndMap - -- liqProvider = (Map.map CE.consolStmt) <$> mLiqProvider - } -- `debug` ("Prepare Done") + t1 {bonds = Map.map (L.patchBondFactor . L.consolStmt) bndMap } appendCollectedCF :: Ast.Asset a => Date -> TestDeal a -> Map.Map PoolId CF.CashFlowFrame -> TestDeal a @@ -984,7 +937,7 @@ appendCollectedCF d t@TestDeal { pool = pt } poolInflowMap txns -> fromMaybe (0,0,0,0,0,0) $ view CF.txnCumulativeStats (last txns) balInCollected = case length txnCollected of 0 -> 0 - _ -> CF.mflowBalance $ last txnCollected + _ -> view CF.tsRowBalance $ last txnCollected txnToAppend = CF.patchCumulative currentStats [] txnCollected accUpdated = Map.adjust (over P.poolFutureTxn (++ txnToAppend)) k acc in @@ -1068,7 +1021,7 @@ populateDealDates (GenericDates m) cu = [ RunWaterfall _d custName | (CustomExeDates custName, custDp) <- custWaterfall , _d <- genSerialDatesTill2 EE closingDate custDp statedDate ] in - Right (coffDate, closingDate, fPayDate, pa, ba, statedDate, cu) -- `debug` ("custom action"++ show cu) + Right (coffDate, closingDate, fPayDate, pa, ba, statedDate, cu) _ -> Left "Missing required dates in GenericDates in deal status PreClosing" @@ -1106,7 +1059,7 @@ populateDealDates (GenericDates m) _ runPool :: Ast.Asset a => P.Pool a -> Maybe AP.ApplyAssumptionType -> Maybe [RateAssumption] -> Either String [(CF.CashFlowFrame, Map.Map CutoffFields Balance)] -- schedule cashflow just ignores the interest rate assumption -runPool (P.Pool [] (Just cf) _ asof _ _ ) Nothing _ = Right $ [(cf, Map.empty)] +runPool (P.Pool [] (Just cf) _ asof _ _ ) Nothing _ = Right [(cf, Map.empty)] -- schedule cashflow with stress assumption runPool (P.Pool [] (Just (CF.CashFlowFrame _ txn)) _ asof _ (Just dp)) (Just (AP.PoolLevel assumps)) mRates = sequenceA [ Ast.projCashflow (ACM.ScheduleMortgageFlow asof txn dp) asof assumps mRates ] -- `debug` ("PROJ in schedule flow") @@ -1114,15 +1067,15 @@ runPool (P.Pool [] (Just (CF.CashFlowFrame _ txn)) _ asof _ (Just dp)) (Just (AP -- project contractual cashflow if nothing found in pool perf assumption -- use interest rate assumption runPool (P.Pool as _ _ asof _ _) Nothing mRates - -- = Right $ map (\x -> (Ast.calcCashflow x asof mRates,Map.empty)) as = do - cf <- sequenceA $ map (\x -> Ast.calcCashflow x asof mRates) as + cf <- sequenceA $ parMap rdeepseq + (\x -> Ast.calcCashflow x asof mRates) + as return [ (x, Map.empty) | x <- cf ] - -- asset cashflow with credit stress ---- By pool level runPool (P.Pool as Nothing Nothing asof _ _) (Just (AP.PoolLevel assumps)) mRates - = sequenceA $ map (\x -> Ast.projCashflow x asof assumps mRates) as + = sequenceA $ parMap rdeepseq (\x -> Ast.projCashflow x asof assumps mRates) as ---- By index runPool (P.Pool as Nothing Nothing asof _ _) (Just (AP.ByIndex idxAssumps)) mRates = let @@ -1130,16 +1083,22 @@ runPool (P.Pool as Nothing Nothing asof _ _) (Just (AP.ByIndex idxAssumps)) mRat in do _assumps <- sequenceA $ map (AP.lookupAssumptionByIdx idxAssumps) [0..(pred numAssets)] -- `debug` ("Num assets"++ show numAssets) - sequenceA $ zipWith (\x a -> Ast.projCashflow x asof a mRates) as _assumps + sequenceA $ parMap rdeepseq (\(x, a) -> Ast.projCashflow x asof a mRates) (zip as _assumps) + ---- By Obligor runPool (P.Pool as Nothing Nothing asof _ _) (Just (AP.ByObligor obligorRules)) mRates = let -- result cf,rules,assets -- matchAssets:: Ast.Asset c => [Either String (CF.CashFlowFrame, Map.Map CutoffFields Balance)] -> [AP.ObligorStrategy] -- -> [c] -> Either String [(CF.CashFlowFrame, Map.Map CutoffFields Balance)] - matchAssets [] _ [] = Right $ [(CF.CashFlowFrame (0,epocDate,Nothing) [], Map.empty)] - matchAssets cfs [] [] = sequenceA cfs - matchAssets cfs [] astList = sequenceA $ cfs ++ ((\x -> (\y -> (y, Map.empty)) <$> (Ast.calcCashflow x asof mRates)) <$> astList) + matchAssets [] _ [] = Right [(CF.CashFlowFrame (0,epocDate,Nothing) [], Map.empty)] + matchAssets cfs [] [] = sequenceA cfs + -- matchAssets cfs [] astList = sequenceA $ cfs ++ ((\x -> (\y -> (y, Map.empty)) <$> (Ast.calcCashflow x asof mRates)) <$> astList) + matchAssets cfs [] astList = let + poolCfs = parMap rdeepseq (\x -> Ast.calcCashflow x asof mRates) astList + poolCfs' = (\x -> (, Map.empty) <$> x) <$> poolCfs + in + sequenceA $ cfs ++ poolCfs' matchAssets cfs (rule:rules) astList = case rule of AP.ObligorById ids assetPerf @@ -1150,7 +1109,7 @@ runPool (P.Pool as Nothing Nothing asof _ _) (Just (AP.ByObligor obligorRules)) Just oid -> S.member oid idSet Nothing -> False) astList - matchedCfs = (\x -> Ast.projCashflow x asof assetPerf mRates) <$> matchedAsts + matchedCfs = parMap rdeepseq (\x -> Ast.projCashflow x asof assetPerf mRates) matchedAsts in matchAssets (cfs ++ matchedCfs) rules unMatchedAsts AP.ObligorByTag tags tagRule assetPerf -> @@ -1164,13 +1123,12 @@ runPool (P.Pool as Nothing Nothing asof _ _) (Just (AP.ByObligor obligorRules)) matchRuleFn (AP.TagNot tRule) s1 s2 = not $ matchRuleFn tRule s1 s2 (matchedAsts,unMatchedAsts) = partition (\x -> matchRuleFn tagRule (Ast.getObligorTags x) obrTags) astList - matchedCfs = (\x -> Ast.projCashflow x asof assetPerf mRates) <$> matchedAsts + matchedCfs = parMap rdeepseq (\x -> Ast.projCashflow x asof assetPerf mRates) matchedAsts in matchAssets (cfs ++ matchedCfs) rules unMatchedAsts AP.ObligorByField fieldRules assetPerf -> let - matchRuleFn (AP.FieldIn fv fvals) Nothing = False matchRuleFn (AP.FieldIn fv fvals) (Just fm) = case Map.lookup fv fm of Just (Left v) -> v `elem` fvals @@ -1196,12 +1154,12 @@ runPool (P.Pool as Nothing Nothing asof _ _) (Just (AP.ByObligor obligorRules)) matchRulesFn fs fm = all (`matchRuleFn` fm) fs (matchedAsts,unMatchedAsts) = partition (matchRulesFn fieldRules . Ast.getObligorFields) astList - matchedCfs = (\x -> Ast.projCashflow x asof assetPerf mRates) <$> matchedAsts + matchedCfs = parMap rdeepseq (\x -> Ast.projCashflow x asof assetPerf mRates) matchedAsts in matchAssets (cfs ++ matchedCfs) rules unMatchedAsts AP.ObligorByDefault assetPerf -> matchAssets - (cfs ++ ((\x -> Ast.projCashflow x asof assetPerf mRates) <$> astList)) + (cfs ++ (parMap rdeepseq (\x -> Ast.projCashflow x asof assetPerf mRates) astList)) [] [] @@ -1317,6 +1275,10 @@ runPoolType (ResecDeal dm) mAssumps mNonPerfAssump getInits :: Ast.Asset a => TestDeal a -> Maybe AP.ApplyAssumptionType -> Maybe AP.NonPerfAssumption -> Either String (TestDeal a,[ActionOnDate], Map.Map PoolId CF.CashFlowFrame, Map.Map PoolId CF.CashFlowFrame) getInits t@TestDeal{fees=feeMap,pool=thePool,status=status,bonds=bndMap} mAssumps mNonPerfAssump = + let + expandInspect sd ed (AP.InspectPt dp ds) = [ InspectDS _d [ds] | _d <- genSerialDatesTill2 II sd dp ed ] + expandInspect sd ed (AP.InspectRpt dp dss) = [ InspectDS _d dss | _d <- genSerialDatesTill2 II sd dp ed ] + in do (startDate,closingDate,firstPayDate,pActionDates,bActionDates,endDate,custWdates) <- populateDealDates (dates t) status @@ -1340,11 +1302,8 @@ getInits t@TestDeal{fees=feeMap,pool=thePool,status=status,bonds=bndMap} mAssump ++ [ ResetLiqProviderRate _d _liqName |(_liqName,__liqResetDates) <- _liqRateResetDates , _d <- __liqResetDates ] --inspect dates - let expandInspect (AP.InspectPt dp ds) = [ InspectDS _d [ds] | _d <- genSerialDatesTill2 II startDate dp endDate ] - let expandInspect (AP.InspectRpt dp dss) = [ InspectDS _d dss | _d <- genSerialDatesTill2 II startDate dp endDate ] - let inspectDates = case mNonPerfAssump of - Just AP.NonPerfAssumption{AP.inspectOn = Just inspectList } -> concat $ expandInspect <$> inspectList + Just AP.NonPerfAssumption{AP.inspectOn = Just inspectList } -> concat $ (expandInspect startDate endDate) <$> inspectList _ -> [] let financialRptDates = case mNonPerfAssump of @@ -1478,8 +1437,8 @@ getInits t@TestDeal{fees=feeMap,pool=thePool,status=status,bonds=bndMap} mAssump let poolWithSchedule = patchScheduleFlow pUnstressedAfterCutoff thePool -- `debug` ("D") let poolWithIssuanceBalance = patchIssuanceBalance status (Map.map (\case - [] -> 0 - txns -> (CF.mflowBegBalance . head) txns) + [] -> 0 + txns -> (CF.mflowBegBalance . head) txns) poolAggCfM) poolWithSchedule let poolWithRunPoolBalance = patchRuntimeBal (Map.map (\(CF.CashFlowFrame (b,_,_) _) -> b) pCollectionCfAfterCutoff) poolWithIssuanceBalance @@ -1490,16 +1449,17 @@ getInits t@TestDeal{fees=feeMap,pool=thePool,status=status,bonds=bndMap} mAssump , pUnstressedAfterCutoff) -- ^ UI translation : to read pool cash -readProceeds :: PoolSource -> CF.TsRow -> Balance -readProceeds CollectedInterest = CF.mflowInterest -readProceeds CollectedPrincipal = CF.mflowPrincipal -readProceeds CollectedRecoveries = CF.mflowRecovery -readProceeds CollectedPrepayment = CF.mflowPrepayment -readProceeds CollectedRental = CF.mflowRental -readProceeds CollectedPrepaymentPenalty = CF.mflowPrepaymentPenalty -readProceeds CollectedCash = CF.tsTotalCash -readProceeds CollectedFeePaid = CF.mflowFeePaid -readProceeds a = error $ "failed to read pool cashflow rule"++show a +-- TODO: need to make this a Maybe +readProceeds :: PoolSource -> CF.TsRow -> Either String Balance +readProceeds CollectedInterest x = Right $ CF.mflowInterest x +readProceeds CollectedPrincipal x = Right $ CF.mflowPrincipal x +readProceeds CollectedRecoveries x = Right $ CF.mflowRecovery x +readProceeds CollectedPrepayment x = Right $ CF.mflowPrepayment x +readProceeds CollectedRental x = Right $ CF.mflowRental x +readProceeds CollectedPrepaymentPenalty x = Right $ CF.mflowPrepaymentPenalty x +readProceeds CollectedCash x = Right $ CF.tsTotalCash x +readProceeds CollectedFeePaid x = Right $ CF.mflowFeePaid x +readProceeds a _ = Left $ " Failed to find pool cashflow field from pool cashflow rule "++show a extractTxnsFromFlowFrameMap :: Maybe [PoolId] -> Map.Map PoolId CF.CashFlowFrame -> [CF.TsRow] @@ -1512,30 +1472,36 @@ extractTxnsFromFlowFrameMap mPids pflowMap = -- extractTxns m = concatMap $ (view CF.cashflowTxn) $ Map.elems m -- ^ deposit cash to account by collection rule -depositInflow :: Date -> W.CollectionRule -> Map.Map PoolId CF.CashFlowFrame -> Map.Map AccountName A.Account -> Map.Map AccountName A.Account +depositInflow :: Date -> W.CollectionRule -> Map.Map PoolId CF.CashFlowFrame -> Map.Map AccountName A.Account -> Either String (Map.Map AccountName A.Account) depositInflow d (W.Collect mPids s an) pFlowMap amap - = Map.adjust (A.deposit amt d (PoolInflow mPids s)) an amap -- `debug` ("Date"++show d++"Deposit"++show amt++"Rule"++show s ++">>AN"++ show an) + = do + amts <- sequenceA $ readProceeds s <$> txns + let amt = sum amts + return $ Map.adjust (A.deposit amt d (PoolInflow mPids s)) an amap where txns = extractTxnsFromFlowFrameMap mPids pFlowMap - amt = sum $ readProceeds s <$> txns depositInflow d (W.CollectByPct mPids s splitRules) pFlowMap amap --TODO need to check 100% - = foldr - (\(accName,accAmt) accM -> - Map.adjust (A.deposit accAmt d (PoolInflow mPids s)) accName accM) - amap - amtsToAccs + = do + amts <- sequenceA $ readProceeds s <$> txns + let amt = sum amts + let amtsToAccs = [ (an, mulBR amt splitRate) | (splitRate, an) <- splitRules] + return $ + foldr + (\(accName,accAmt) accM -> + Map.adjust (A.deposit accAmt d (PoolInflow mPids s)) accName accM) + amap + amtsToAccs where - amtsToAccs = [ (an, mulBR amt splitRate) | (splitRate, an) <- splitRules] txns = extractTxnsFromFlowFrameMap mPids pFlowMap - amt = sum $ readProceeds s <$> txns -depositInflow _ a _ _ = error $ "Failed to match collection rule"++ show a +depositInflow _ a _ _ = Left $ " Failed to match collection rule "++ show a -- ^ deposit cash to account by pool map CF and rules -depositPoolFlow :: [W.CollectionRule] -> Date -> Map.Map PoolId CF.CashFlowFrame -> Map.Map String A.Account -> Map.Map String A.Account +depositPoolFlow :: [W.CollectionRule] -> Date -> Map.Map PoolId CF.CashFlowFrame -> Map.Map String A.Account -> Either String (Map.Map String A.Account) depositPoolFlow rules d pFlowMap amap - = foldr (\rule acc -> depositInflow d rule pFlowMap acc) amap rules + -- = foldr (\rule acc -> depositInflow d rule pFlowMap acc) amap rules + = foldM (\acc rule -> depositInflow d rule pFlowMap acc) amap rules $(deriveJSON defaultOptions ''ExpectReturn) \ No newline at end of file diff --git a/src/Deal/DealAction.hs b/src/Deal/DealAction.hs index 6b953de2..be971264 100644 --- a/src/Deal/DealAction.hs +++ b/src/Deal/DealAction.hs @@ -198,6 +198,21 @@ calcDueFee t calcDay f@(F.Fee fn (F.AmtByTbl _ ds tbl) fs fd fdday fa lpd _) let dueAmt = fromMaybe 0.0 $ lookupTable tbl Up ( fromRational lookupVal >=) return f {F.feeDue = dueAmt + fd, F.feeDueDate = Just calcDay} + +calcDueFee t calcDay f@(F.Fee fn (F.FeeFlowByPoolPeriod pc) fs fd fdday fa lpd stmt) + = do + currentPoolPeriod <- queryCompound t calcDay (DealStatInt PoolCollectedPeriod) + feePaidAmt <- queryCompound t calcDay (FeePaidAmt [fn]) + let dueAmt = fromMaybe 0 $ getValFromPerCurve pc Past Inc (succ (floor (fromRational currentPoolPeriod))) + return f {F.feeDue = max 0 (dueAmt - fromRational feePaidAmt) + fd, F.feeDueDate = Just calcDay} + +calcDueFee t calcDay f@(F.Fee fn (F.FeeFlowByBondPeriod pc) fs fd fdday fa lpd stmt) + = do + currentBondPeriod <- queryCompound t calcDay (DealStatInt BondPaidPeriod) + feePaidAmt <- queryCompound t calcDay (FeePaidAmt [fn]) + let dueAmt = fromMaybe 0 $ getValFromPerCurve pc Past Inc (succ (floor (fromRational currentBondPeriod))) + return f {F.feeDue = max 0 (dueAmt - fromRational feePaidAmt) + fd, F.feeDueDate = Just calcDay} + disableLiqProvider :: Ast.Asset a => TestDeal a -> Date -> CE.LiqFacility -> CE.LiqFacility disableLiqProvider _ d liq@CE.LiqFacility{CE.liqEnds = Just endDate } | d > endDate = liq{CE.liqCredit = Just 0} @@ -239,12 +254,6 @@ calcDueInt t d mBal mRate b@(L.Bond _ _ oi io _ bal r dp _ di Nothing _ lastPrin calcDueInt t calc_date _ _ b@(L.Bond bn L.Z bo bi _ bond_bal bond_rate _ _ _ _ lstIntPay _ _) = Right $ b {L.bndDueInt = 0 } --- accured by yield -calcDueInt t d _ _ b@(L.Bond bn L.Equity bo (L.InterestByYield y) _ bond_bal _ _ int_due _ _ lstIntPay _ mStmt) - = Right $ b {L.bndDueInt = newDue } -- `debug` ("Yield Due Int >>"++ show bn++">> new due"++ show newDue++">> old due"++ show int_due ) - where - newDue = L.backoutDueIntByYield d b - calcDueInt t d _ _ b@(L.Bond _ L.Equity _ _ _ _ _ _ _ _ _ _ _ _) = Right $ b @@ -283,54 +292,58 @@ calcDuePrin t d b@(L.BondGroup bMap) m <- sequenceA $ Map.map (calcDuePrin t d) bMap return $ L.BondGroup m -calcDuePrin t d b@(L.Bond _ L.Sequential _ _ _ bondBal _ _ _ _ _ _ _ _) - = Right $ b {L.bndDuePrin = bondBal } - -calcDuePrin t d b@(L.Bond bn (L.Lockout cd) bo bi _ bondBal _ _ _ _ _ _ _ _) - | cd > d = Right $ b {L.bndDuePrin = 0} - | otherwise = Right $ b {L.bndDuePrin = bondBal } +calcDuePrin t d b = + let + bondBal = L.bndBalance b + btype = L.bndType b + in + case btype of + L.Sequential -> Right $ b {L.bndDuePrin = bondBal} + L.Lockout cd | cd > d -> Right $ b {L.bndDuePrin = 0 } + | otherwise -> Right $ b {L.bndDuePrin = bondBal} + L.PAC schedule -> + let + scheduleDue = getValOnByDate schedule d + duePrin = max (bondBal - scheduleDue) 0 + in + Right $ b {L.bndDuePrin = duePrin} + L.AmtByPeriod schedule -> + let + currentBondPaidPeriod = succ $ fromMaybe 0 $ getDealStatInt t BondPaidPeriod + in + case getValFromPerCurve schedule Past Inc currentBondPaidPeriod of + Nothing -> Left $ "Failed to find due principal from period curve at index" ++ show currentBondPaidPeriod + Just scheduleDue -> Right $ b {L.bndDuePrin = max (bondBal - scheduleDue) 0 } + L.PacAnchor schedule bns -> + let + scheduleDue = getValOnByDate schedule d + in + do + anchor_bond_flag <- queryDealBool t (IsOutstanding bns) d + let duePrin = if anchor_bond_flag then + max (bondBal - scheduleDue) 0 + else + bondBal + return $ b {L.bndDuePrin = duePrin} + L.Z -> Right $ + if all isZbond activeBnds then + b {L.bndDuePrin = bond_bal} + else + b {L.bndDuePrin = 0, L.bndBalance = new_bal, L.bndLastIntPay=Just d} -- `debug` ("bn >> "++bn++"Due Prin set=>"++show(duePrin) ) + where + isZbond (L.Bond _ L.Z _ _ _ _ _ _ _ _ _ _ _ _) = True + isZbond L.Bond {} = False + bond_bal = L.bndBalance b + lstIntPay = L.bndLastIntPay b + + activeBnds = filter (not . L.isPaidOff) (Map.elems (bonds t)) + new_bal = bond_bal + dueInt + lastIntPayDay = case lstIntPay of + Just pd -> pd + Nothing -> getClosingDate (dates t) + dueInt = IR.calcInt bond_bal lastIntPayDay d (L.getCurRate b) DC_ACT_365F + L.Equity -> Right $ b {L.bndDuePrin = bondBal } -calcDuePrin t d b@(L.Bond bn (L.PAC schedule) _ _ _ bondBal _ _ _ _ _ _ _ _) - = Right $ b {L.bndDuePrin = duePrin} - where - scheduleDue = getValOnByDate schedule d - duePrin = max (bondBal - scheduleDue) 0 - -calcDuePrin t d b@(L.Bond bn (L.PacAnchor schedule bns) _ _ _ bondBal _ _ _ _ _ _ _ _) - = let - scheduleDue = getValOnByDate schedule d - in - do - anchor_bond_balance <- queryCompound t d (CurrentBondBalanceOf bns) - let duePrin = if anchor_bond_balance > 0 then - max (bondBal - scheduleDue) 0 - else - bondBal - return $ b {L.bndDuePrin = duePrin} -- `debug` ("bn >> "++bn++"Due Prin set=>"++show(duePrin) ) - -calcDuePrin t calc_date b@(L.Bond bn L.Z bo bi _ bond_bal bond_rate prin_arr int_arrears _ _ lstIntPay _ _) - = Right $ - if all isZbond activeBnds then - b {L.bndDuePrin = bond_bal} -- `debug` ("bn >> "++bn++"Due Prin set=>"++show(duePrin) ) - else - b {L.bndDuePrin = 0, L.bndBalance = new_bal, L.bndLastIntPay=Just calc_date} -- `debug` ("bn >> "++bn++"Due Prin set=>"++show(duePrin) ) - where - isZbond (L.Bond _ L.Z _ _ _ _ _ _ _ _ _ _ _ _) = True - isZbond L.Bond {} = False - - activeBnds = filter (\x -> L.bndBalance x > 0) (Map.elems (bonds t)) - new_bal = bond_bal + dueInt - lastIntPayDay = case lstIntPay of - Just pd -> pd - Nothing -> getClosingDate (dates t) - dueInt = IR.calcInt bond_bal lastIntPayDay calc_date bond_rate DC_ACT_365F - -calcDuePrin t calc_date b@(L.Bond bn L.Equity bo bi _ bondBal _ _ _ _ _ _ _ _) - = Right $ b {L.bndDuePrin = bondBal } - --- TODO: add more generic to handle with MultiIntBond -calcDuePrin t d b@(L.MultiIntBond _ L.Sequential _ _ _ bondBal _ _ _ _ _ _ _ _) - = Right $ b {L.bndDuePrin = bondBal } priceAssetUnion :: ACM.AssetUnion -> Date -> PricingMethod -> AP.AssetPerf -> Maybe [RateAssumption] -> Either String PriceResult @@ -527,7 +540,7 @@ calcAvailAfterLimit t d acc mSupport dueAmt mLimit Just (DuePct pct) -> min (mulBR dueAmt pct) <$> availFund _ -> Left ("Failed to find type"++ show mLimit) if r < 0 then - (Left ("Negative value when calculates Limit:"++ show mLimit)) + (Left ("Negative value when calculates Limit:"++ show mLimit++ "but got from availFund"++ show availFund)) else return r @@ -625,6 +638,14 @@ performActionWrap d (W.BuyAsset ml pricingMethod accName _) = Left $ "Date:"++ show d ++"Missing revolving Assumption(asset assumption & asset to buy)" ++ name t +performActionWrap d + (t + ,rc@RunContext{runPoolFlow=pcf + ,revolvingAssump=Nothing + ,revolvingInterestRateAssump=mRates} + ,logs) + (W.BuyAssetFrom _ _ _ _ _) + = Left $ "Date:"++ show d ++"Missing revolving Assumption(asset assumption & asset to buy)" ++ name t -- TODO need to set a limit to sell performActionWrap d (t@TestDeal{accounts = accMap, pool = pt} @@ -912,7 +933,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap,ledgers= Just ledgerM} do paidOutAmt <- actualPaidOut let (bondsPaid, remainAmt) = payProRata d paidOutAmt L.getTotalDueInt (L.payInt d) bndsList - let accPaidOut = (min availAccBal paidOutAmt) + let accPaidOut = min availAccBal paidOutAmt let newLedgerM = Map.adjust (LD.entryLogByDr dr paidOutAmt d Nothing) lName ledgerM let dealAfterAcc = t {accounts = Map.adjust (A.draw accPaidOut d (PayInt bnds)) an accMap @@ -944,10 +965,8 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntResidual mLimi , bonds = Map.adjust (L.payYield d limitAmt) bndName bndMap} --- TODO index out of bound check -- TODO check for multi interest bond -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} - (W.PayIntByRateIndex mLimit an bndNames idx mSupport) +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntByRateIndex mLimit an bndNames idx mSupport) = let availAccBal = A.accBalance (accMap Map.! an) bndsList = filter (is L._MultiIntBond) $ (Map.!) bndMap <$> bndNames @@ -1147,8 +1166,7 @@ performAction d t@TestDeal{accounts=accMap, bonds=bndMap} (W.FundWith mlimit an _ -> Left $ "Date:"++show d ++"Not valid limit for funding with bond"++ show bnd let fundAmt = fromRational fundAmt_ let accMapAfterFund = Map.adjust (A.deposit fundAmt d (FundWith bnd fundAmt)) an accMap - newBnd <- calcDueInt t d Nothing Nothing $ bndMap Map.! bnd - let bndFunded = L.fundWith d fundAmt newBnd + let bndFunded = L.fundWith d fundAmt $ bndMap Map.! bnd return $ t {accounts = accMapAfterFund, bonds= Map.fromList [(bnd,bndFunded)] <> bndMap } -- ^ write off bonds and book @@ -1161,8 +1179,7 @@ performAction d t@TestDeal{bonds = bndMap, ledgers = Just ledgerM } do writeAmt <- applyLimit t d bndBal bndBal mLimit let newLedgerM = Map.adjust (LD.entryLogByDr dr writeAmt d (Just (WriteOff bnd writeAmt))) lName ledgerM - newBnd <- calcDueInt t d Nothing Nothing bndToWriteOff - let bndWritedOff = L.writeOff d writeAmt newBnd + bndWritedOff <- L.writeOff d writeAmt bndToWriteOff return $ t {bonds = Map.fromList [(bnd,bndWritedOff)] <> bndMap, ledgers = Just newLedgerM} performAction d t@TestDeal{bonds=bndMap} (W.WriteOff mlimit bnd) @@ -1174,8 +1191,7 @@ performAction d t@TestDeal{bonds=bndMap} (W.WriteOff mlimit bnd) x -> Left $ "Date:"++show d ++"not supported type to determine the amount to write off"++ show x let writeAmtCapped = min (fromRational writeAmt) $ L.bndBalance $ bndMap Map.! bnd - newBnd <- calcDueInt t d Nothing Nothing $ bndMap Map.! bnd - let bndWritedOff = L.writeOff d writeAmtCapped newBnd + bndWritedOff <- L.writeOff d writeAmtCapped $ bndMap Map.! bnd return $ t {bonds = Map.fromList [(bnd,bndWritedOff)] <> bndMap} performAction d t@TestDeal{bonds=bndMap, ledgers = Just ledgerM} @@ -1183,8 +1199,9 @@ performAction d t@TestDeal{bonds=bndMap, ledgers = Just ledgerM} = do bndsToWriteOff <- mapM (calcDueInt t d Nothing Nothing . (bndMap Map.!)) bnds let totalBondBal = sum $ L.bndBalance <$> bndsToWriteOff + -- total amount to be write off writeAmt <- applyLimit t d totalBondBal totalBondBal mLimit - let (bndWrited, _) = paySequentially d writeAmt L.bndBalance (L.writeOff d) [] bndsToWriteOff + (bndWrited, _) <- paySeqM d writeAmt L.bndBalance (L.writeOff d) (Right []) bndsToWriteOff let bndMapUpdated = lstToMapByFn L.bndName bndWrited let newLedgerM = Map.adjust (LD.entryLogByDr dr writeAmt d Nothing) lName ledgerM return t {bonds = bndMapUpdated <> bndMap, ledgers = Just newLedgerM} @@ -1195,7 +1212,7 @@ performAction d t@TestDeal{bonds=bndMap } (W.WriteOffBySeq mLimit bnds) bondsToWriteOff <- mapM (calcDueInt t d Nothing Nothing . (bndMap Map.!)) bnds let totalBondBal = sum $ L.bndBalance <$> bondsToWriteOff writeAmt <- applyLimit t d totalBondBal totalBondBal mLimit - let (bndWrited, _) = paySequentially d writeAmt L.bndBalance (L.writeOff d) [] bondsToWriteOff + (bndWrited, _) <- paySeqM d writeAmt L.bndBalance (L.writeOff d) (Right []) bondsToWriteOff let bndMapUpdated = lstToMapByFn L.bndName bndWrited return t {bonds = bndMapUpdated <> bndMap } @@ -1275,7 +1292,7 @@ performAction d t@TestDeal{fees=feeMap,liqProvider = Just _liqProvider} (W.LiqSu totalDueFee <- queryCompound t d (CurrentDueFee fns) supportAmt <- applyLimit t d (fromRational totalDueFee) (fromRational totalDueFee) mLimit - let transferAmt = case (CE.liqCredit liq) of + let transferAmt = case CE.liqCredit liq of Nothing -> supportAmt (Just v) -> min supportAmt v @@ -1294,7 +1311,7 @@ performAction d t@TestDeal{bonds=bndMap,liqProvider = Just _liqProvider} totalDueInt <- queryCompound t d (CurrentDueBondInt bns) supportAmt <- applyLimit t d (fromRational totalDueInt) (fromRational totalDueInt) mLimit - let transferAmt = case (CE.liqCredit liq) of + let transferAmt = case CE.liqCredit liq of Nothing -> supportAmt (Just v) -> min supportAmt v diff --git a/src/Deal/DealBase.hs b/src/Deal/DealBase.hs index 98934e17..91d19f63 100644 --- a/src/Deal/DealBase.hs +++ b/src/Deal/DealBase.hs @@ -9,9 +9,11 @@ module Deal.DealBase (TestDeal(..),SPV(..),dealBonds,dealFees,dealAccounts,dealPool,PoolType(..),getIssuanceStats ,getAllAsset,getAllAssetList,getAllCollectedFrame,getLatestCollectFrame,getAllCollectedTxns ,getIssuanceStatsConsol,getAllCollectedTxnsList,dealScheduledCashflow - ,getPoolIds,getBondByName, UnderlyingDeal(..),dealCashflow, uDealFutureTxn,viewDealAllBonds,DateDesp(..),ActionOnDate(..),OverrideType(..) + ,getPoolIds,getBondByName, UnderlyingDeal(..),dealCashflow, uDealFutureTxn,viewDealAllBonds,DateDesp(..),ActionOnDate(..) ,sortActionOnDate,dealBondGroups ,viewDealBondsByNames,poolTypePool,viewBondsInMap,bondGroupsBonds + ,increaseBondPaidPeriod,increasePoolCollectedPeriod + ,DealStatFields(..),getDealStatInt ) where import qualified Accounts as A @@ -60,6 +62,22 @@ import qualified Control.Lens as P debug = flip trace +data DealComp = CompBond + | CompAccount + | CompFee + | CompPool + | CompTrigger + | CompLedger + | CompRateSwap + | CompRateCap + | CompCurrencySwap + | CompLiqProvider + deriving (Show,Eq,Ord,Generic,Read) + +data ActionTypeOnDate = DoSettle + | DoAccrue + | DoUpdateRate + data ActionOnDate = EarnAccInt Date AccName -- ^ sweep bank account interest | ChangeDealStatusTo Date DealStatus -- ^ change deal status | AccrueFee Date FeeName -- ^ accure fee @@ -154,10 +172,6 @@ sortActionOnDate a1 a2 d2 = getDate a2 -data OverrideType = CustomActionOnDates [ActionOnDate] - deriving (Show,Generic,Ord,Eq) - - type CutoffDate = Date type ClosingDate = Date type RevolvingDate = Date @@ -226,6 +240,11 @@ data PoolType a = MultiPool (Map.Map PoolId (P.Pool a)) deriving (Generic, Eq, Ord, Show) +type BalDealStatMap = Map.Map DealStatFields Balance +type RDealStatMap = Map.Map DealStatFields Rate +type BDealStatMap = Map.Map DealStatFields Bool +type IDealStatMap = Map.Map DealStatFields Int + data TestDeal a = TestDeal { name :: DealName ,status :: DealStatus ,dates :: DateDesp @@ -235,14 +254,13 @@ data TestDeal a = TestDeal { name :: DealName ,pool :: PoolType a ,waterfall :: Map.Map W.ActionWhen W.DistributionSeq ,collects :: [W.CollectionRule] - ,call :: Maybe [C.CallOption] + ,stats :: (BalDealStatMap,RDealStatMap,BDealStatMap,IDealStatMap) ,liqProvider :: Maybe (Map.Map String CE.LiqFacility) ,rateSwap :: Maybe (Map.Map String HE.RateSwap) ,rateCap :: Maybe (Map.Map String HE.RateCap) ,currencySwap :: Maybe (Map.Map String HE.CurrencySwap) ,custom:: Maybe (Map.Map String CustomDataType) ,triggers :: Maybe (Map.Map DealCycle (Map.Map String Trigger)) - ,overrides :: Maybe [OverrideType] ,ledgers :: Maybe (Map.Map String LD.Ledger) } deriving (Show,Generic,Eq,Ord) @@ -266,7 +284,7 @@ instance SPV (TestDeal a) where getBondBegBal t bn = case b of - Nothing -> 0 `debug` ("it is not supposed to happen") + Nothing -> 0 Just bnd -> case L.bndStmt bnd of Just (Statement []) -> L.getCurBalance bnd -- `debug` ("Getting beg bal"++bn++"Last smt"++show (head stmts)) @@ -501,8 +519,33 @@ getAllCollectedTxnsList t mPns where listOfTxns = Map.elems $ getAllCollectedTxns t mPns +increasePoolCollectedPeriod :: TestDeal a -> TestDeal a +increasePoolCollectedPeriod t@TestDeal{stats = (balMap,rateMap,boolMap,intMap)} + = let + intMap' = Map.insertWith (+) PoolCollectedPeriod 1 intMap + in + t {stats = (balMap,rateMap,boolMap,intMap')} + +increaseBondPaidPeriod :: TestDeal a -> TestDeal a +increaseBondPaidPeriod t@TestDeal{stats = (balMap,rateMap,boolMap,intMap)} + = let + intMap' = Map.insertWith (+) BondPaidPeriod 1 intMap + in + t {stats = (balMap,rateMap,boolMap,intMap')} + +getDealStatInt :: TestDeal a -> DealStatFields -> Maybe Int +getDealStatInt t@TestDeal{stats = (balMap,rateMap,boolMap,intMap)} f + = Map.lookup f intMap data UnderBond b = UnderBond BondName Rate (TestDeal b) +opts :: JSONKeyOptions +opts = defaultJSONKeyOptions -- { keyModifier = toLower } + +instance ToJSONKey DealStatFields where + toJSONKey = genericToJSONKey opts +instance FromJSONKey DealStatFields where + fromJSONKey = genericFromJSONKey opts + -$(concat <$> traverse (deriveJSON defaultOptions) [''TestDeal, ''UnderlyingDeal, ''PoolType, ''DateDesp, ''ActionOnDate, ''OverrideType]) +$(concat <$> traverse (deriveJSON defaultOptions) [''TestDeal, ''UnderlyingDeal, ''PoolType, ''DateDesp, ''ActionOnDate]) diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index 20ec4366..0ba9af61 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -41,9 +41,9 @@ import Control.Lens.Extras (is) import Control.Lens.TH import Control.Applicative import Data.Map.Lens +import Data.List.Lens import Debug.Trace import Lib -import Cashflow (CashFlowFrame(CashFlowFrame)) import qualified Cashflow as P debug = flip trace @@ -216,7 +216,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f let latestCfs = filter isJust $ Map.elems $ getLatestCollectFrame t Nothing rates = toRational . maybe 0.0 CF.mflowRate <$> latestCfs - bals = maybe 0.0 CF.mflowBalance <$> latestCfs + bals = maybe 0.0 (view CF.tsRowBalance) <$> latestCfs in Right $ weightedBy (toRational <$> bals) rates @@ -227,6 +227,12 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f in Right $ sum rates + DealStatRate s -> + case stats t of + (_,m,_,_) -> case Map.lookup s m of + Just v -> Right . toRational $ v + Nothing -> Left $ "Date:"++show d++"Failed to query formula of -> "++ show s + -- int query FutureCurrentPoolBorrowerNum _d mPns -> @@ -250,6 +256,13 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f ProjCollectPeriodNum -> Right . toRational $ maximum' $ Map.elems $ Map.map (maybe 0 CF.sizeCashFlowFrame) $ getAllCollectedFrame t Nothing + DealStatInt s -> + case stats t of + (_,_,_,m) -> case Map.lookup s m of + Just v -> Right . toRational $ v + Nothing -> Left $ "Date:"++show d++"Failed to query formula of -> "++ show s + + ReserveBalance ans -> do accBal <- lookupAndApplies (calcTargetAmount t d) ("Date:"++show d++"Cal Reserve Balance") ans accMap @@ -353,7 +366,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f let scheduleFlowM = Map.elems $ view dealScheduledCashflow t in - Right . toRational $ sum $ maybe 0 (CF.mflowBalance . head . view CF.cashflowTxn) <$> scheduleFlowM + Right . toRational $ sum $ maybe 0 ((view CF.tsRowBalance) . head . view CF.cashflowTxn) <$> scheduleFlowM FutureCurrentSchedulePoolBegBalance mPns -> let @@ -515,6 +528,13 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f in sumTxn $ cutBy Inc Past d _txn + FeePaidAmt fns -> + let + fees = (feeMap Map.!) <$> fns + feeTxns = concat [ getTxns (F.feeStmt fee) | fee <- fees ] + in + Right . toRational $ sumTxn feeTxns + BondTxnAmtBy d bns mCmt -> let bnds = viewDealBondsByNames t bns @@ -571,29 +591,36 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f in Right . toRational $ sum $ map ex stmts + -- ^ get total int due for bonds CurrentDueBondInt bns -> Right . toRational $ sum $ L.getDueInt <$> viewDealBondsByNames t bns + -- ^ get total int over int due for bonds + CurrentDueBondIntOverInt bns -> + Right . toRational $ sum $ L.getDueIntOverInt <$> viewDealBondsByNames t bns + + -- ^ get total due (due int + int over int due) for bonds + CurrentDueBondIntTotal bns -> + sum <$> sequenceA (queryCompound t d <$> [CurrentDueBondInt bns,CurrentDueBondIntOverInt bns]) + CurrentDueBondIntAt idx bns -> let bs = filter (is L._MultiIntBond) $ viewDealBondsByNames t bns - dueInts = (\x -> x!!idx) <$> (L.bndDueInts <$> bs) + mDueInts = sequenceA $ (\x -> x ^? ix idx) <$> (L.bndDueInts <$> bs) in - Right . toRational $ sum dueInts - - CurrentDueBondIntOverInt bns -> - Right . toRational $ sum $ L.getDueIntOverInt <$> viewDealBondsByNames t bns + case mDueInts of + Nothing -> Left $ "Date:"++show d++"Failed to find due int at index for bonds"++ show bns ++ "with Index:"++ show idx ++ " but bonds has "++ show (L.bndDueInts <$> bs) + Just dueInts -> Right . toRational $ sum dueInts CurrentDueBondIntOverIntAt idx bns -> let bs = filter (is L._MultiIntBond) $ viewDealBondsByNames t bns - dueInts = (\x -> x!!idx) <$> (L.bndDueIntOverInts <$> bs) + mDueInts = sequenceA $ (\x -> x ^? ix idx) <$> (L.bndDueIntOverInts <$> bs) in - Right . toRational $ sum $ dueInts + case mDueInts of + Nothing -> Left $ "Date:"++show d++"Failed to find due int over int at index for bonds"++ show bns ++ "with Index:"++ show idx ++ " but bonds has "++ show (L.bndDueIntOverInts <$> bs) + Just dueInts -> Right . toRational $ sum $ dueInts - CurrentDueBondIntTotal bns -> - sum <$> sequenceA (queryCompound t d <$> [CurrentDueBondInt bns,CurrentDueBondIntOverInt bns]) - CurrentDueBondIntTotalAt idx bns -> sum <$> sequenceA (queryCompound t d <$> [CurrentDueBondIntAt idx bns,CurrentDueBondIntOverIntAt idx bns]) @@ -675,7 +702,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f Nothing -> Left $ "Failed to get the required amount for target IRR: "++ bondName++" Rate:"++ show irr Just amt -> Right $ if oDate <= d then - (toRational amt) + toRational amt else 0.0 @@ -689,6 +716,12 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f Just (CustomDS ds) -> queryCompound t d (patchDateToStats d ds ) _ -> Left $ "Date:"++show d++"Unsupported custom data found for key " ++ show s + DealStatBalance s -> + case stats t of + (m,_,_,_) -> case Map.lookup s m of + Just v -> Right . toRational $ v + Nothing -> Left $ "Date:"++show d++"Failed to query formula of -> "++ show s + _ -> Left ("Date:"++show d++"Failed to query formula of -> "++ show s) @@ -744,6 +777,15 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap} ds d = IsDealStatus st -> Right $ status t == st + + DealStatBool s -> + case stats t of + (_,_,m,_) -> case Map.lookup s m of + Just v -> Right v + Nothing -> Left $ "Date:"++show d++"Failed to query formula of -> "++ show s + + + TestNot ds -> do not <$> (queryDealBool t ds d) -- TestAny b dss -> b `elem` [ queryDealBool t ds d | ds <- dss ] TestAny b dss -> anyM (\ x -> (== b) <$> queryDealBool t x d ) dss diff --git a/src/Deal/DealValidation.hs b/src/Deal/DealValidation.hs index ef06062e..f856b4bd 100644 --- a/src/Deal/DealValidation.hs +++ b/src/Deal/DealValidation.hs @@ -308,7 +308,7 @@ extractRequiredRates t@TestDeal{accounts = accM validateAggRule :: [W.CollectionRule] -> [PoolId] -> [ResultComponent] validateAggRule rules validPids = [ ErrorMsg ("Pool source "++show ps++" has a weight of "++show r) | ((pid,ps),r) <- Map.toList oustandingPs ] ++ - [ ErrorMsg ("Pool Id not found"++show ospid++" in "++ show validPids) | ospid <- osPid ] + [ ErrorMsg ("Pool Id not found "++show ospid++" in "++ show validPids) | ospid <- osPid ] where countWeight (W.Collect (Just pids) ps _) = Map.fromList [((pid,ps),1.0) | pid <- pids] countWeight (W.Collect Nothing ps _) = Map.fromList [((PoolConsol,ps),1.0)] diff --git a/src/Expense.hs b/src/Expense.hs index e922ce0f..3fc9c465 100644 --- a/src/Expense.hs +++ b/src/Expense.hs @@ -41,6 +41,8 @@ data FeeType = AnnualRateFee DealStats FormulaRate -- ^ an | AmtByTbl DatePattern DealStats (Table Balance Balance) -- ^ lookup query value in a table | TargetBalanceFee DealStats DealStats -- ^ fee due amount = max( 0, (ds1 - ds2)) | FeeFlow Ts -- ^ a time series based fee + | FeeFlowByPoolPeriod (PerCurve Balance) -- ^ a pool index series based fee + | FeeFlowByBondPeriod (PerCurve Balance) -- ^ a bond index series based fee | ByCollectPeriod Amount -- ^ fix amount per collection period deriving (Show,Eq, Generic,Ord) @@ -60,10 +62,7 @@ payFee :: Date -- ^ When pay action happen -> Fee -- ^ Fee before being paid -> Fee -- ^ Fee after paid payFee d amt f@(Fee fn ft fs fd fdDay fa flpd fstmt) = - f {feeLastPaidDay = Just d - ,feeDue = dueRemain - ,feeArrears = arrearRemain - ,feeStmt = newStmt} + f {feeLastPaidDay = Just d ,feeDue = dueRemain ,feeArrears = arrearRemain ,feeStmt = newStmt} where [(r0,arrearRemain),(r1,dueRemain)] = paySeqLiabilities amt [fa,fd] paid = fa + fd - arrearRemain - dueRemain @@ -72,10 +71,7 @@ payFee d amt f@(Fee fn ft fs fd fdDay fa flpd fstmt) = -- | pay amount of fee regardless the due amount payResidualFee :: Date -> Amount -> Fee -> Fee payResidualFee d amt f@(Fee fn ft fs fd fdDay fa flpd fstmt) = - f {feeLastPaidDay = Just d - ,feeDue = dueRemain - ,feeArrears = arrearRemain - ,feeStmt = newStmt} + f {feeLastPaidDay = Just d ,feeDue = dueRemain ,feeArrears = arrearRemain ,feeStmt = newStmt} where [(r0,arrearRemain),(r1,dueRemain)] = paySeqLiabilities amt [fa,fd] newStmt = appendStmt (ExpTxn d dueRemain amt arrearRemain (PayFee fn)) fstmt @@ -115,4 +111,4 @@ instance IR.UseRate Fee where makeLensesFor [("feeName","feeNameLens"),("feeType","feeTypeLens") ,("feeDue","feeDueLens") ,("feeDueDate","feeDueDateLens") ,("feeStmt","feeStmtLens")] ''Fee $(deriveJSON defaultOptions ''FeeType) -$(deriveJSON defaultOptions ''Fee) +$(deriveJSON defaultOptions ''Fee) \ No newline at end of file diff --git a/src/Ledger.hs b/src/Ledger.hs index ceeaa33e..34a7a96d 100644 --- a/src/Ledger.hs +++ b/src/Ledger.hs @@ -58,7 +58,7 @@ entryLogByDr Debit amt d (Just (TxnComments cms)) = entryLog amt d (TxnComments hasTxnDirection :: TxnComment -> Bool hasTxnDirection (TxnDirection _) = True -hasTxnDirection (TxnComments txns) = any (hasTxnDirection) txns +hasTxnDirection (TxnComments txns) = any hasTxnDirection txns hasTxnDirection _ = False isTxnDirection :: BookDirection -> TxnComment -> Bool diff --git a/src/Liability.hs b/src/Liability.hs index d169d5cf..ee1f1748 100644 --- a/src/Liability.hs +++ b/src/Liability.hs @@ -7,7 +7,7 @@ module Liability (Bond(..),BondType(..),OriginalInfo(..) - ,payInt,payPrin,consolStmt,backoutDueIntByYield,isPaidOff,getCurBalance + ,payInt,payPrin,consolStmt,isPaidOff,getCurBalance ,priceBond,PriceResult(..),pv,InterestInfo(..),RateReset(..) ,getDueInt ,weightAverageBalance,calcZspread,payYield,getTotalDueInt @@ -19,7 +19,7 @@ module Liability ,accrueInt,stepUpInterestInfo,payIntByIndex,_MultiIntBond ,getDueIntAt,getDueIntOverIntAt,getDueIntOverInt,getTotalDueIntAt ,getCurRate - ,bondCashflow + ,bondCashflow,getOutstandingAmount ) where @@ -57,6 +57,7 @@ import Control.Lens hiding (Index) import Control.Lens.TH import Language.Haskell.TH.Lens import Stmt (getTxnAmt) +import Numeric.RootFinding debug = flip trace @@ -65,7 +66,6 @@ isAdjustble :: InterestInfo -> Bool isAdjustble Floater {} = True isAdjustble RefRate {} = True isAdjustble Fix {} = False -isAdjustble InterestByYield {} = False isAdjustble (CapRate r _ ) = isAdjustble r isAdjustble (FloorRate r _ ) = isAdjustble r isAdjustble (WithIoI r _) = isAdjustble r @@ -82,7 +82,6 @@ isStepUp _ = True getIndexFromInfo :: InterestInfo -> Maybe [Index] getIndexFromInfo (Floater _ idx _ _ _ _ _) = Just [idx] getIndexFromInfo Fix {} = Nothing -getIndexFromInfo InterestByYield {} = Nothing getIndexFromInfo RefRate {} = Nothing getIndexFromInfo (CapRate info _) = getIndexFromInfo info getIndexFromInfo (FloorRate info _) = getIndexFromInfo info @@ -91,7 +90,6 @@ getIndexFromInfo (WithIoI info _) = getIndexFromInfo info getDayCountFromInfo :: InterestInfo -> Maybe DayCount getDayCountFromInfo (Floater _ _ _ _ dc _ _) = Just dc getDayCountFromInfo (Fix _ dc) = Just dc -getDayCountFromInfo InterestByYield {} = Nothing getDayCountFromInfo RefRate {} = Nothing getDayCountFromInfo (CapRate info _) = getDayCountFromInfo info getDayCountFromInfo (FloorRate info _) = getDayCountFromInfo info @@ -109,7 +107,6 @@ data InterestOverInterestType = OverCurrRateBy Rational -- ^ inflat ioi rate by --------------------------- start Rate, index, spread, reset dates, daycount, floor, cap data InterestInfo = Floater IRate Index Spread RateReset DayCount (Maybe Floor) (Maybe Cap) | Fix IRate DayCount -- ^ fixed rate - | InterestByYield IRate | RefRate IRate DealStats Float RateReset -- ^ interest rate depends to a formula | CapRate InterestInfo IRate -- ^ cap rate | FloorRate InterestInfo IRate -- ^ floor rate @@ -117,8 +114,6 @@ data InterestInfo = Floater IRate Index Spread RateReset DayCount (Maybe Floor) deriving (Show, Eq, Generic, Ord, Read) --- data StepUp = PassDateSpread Date Spread -- ^ add a spread on a date and effective afterwards --- | PassDateLadderSpread Date Spread RateReset -- ^ add a spread on the date pattern stepUpInterestInfo :: StepUp -> InterestInfo -> InterestInfo stepUpInterestInfo sp ii = case ii of @@ -132,6 +127,7 @@ stepUpInterestInfo sp ii = getSpread (PassDateSpread _ s) = s getSpread (PassDateLadderSpread _ s _) = s + getDpFromIntInfo :: InterestInfo -> Maybe DatePattern getDpFromIntInfo (Floater _ _ _ dp _ _ _) = Just dp getDpFromIntInfo (RefRate _ _ _ dp) = Just dp @@ -140,6 +136,7 @@ getDpFromIntInfo (FloorRate ii _) = getDpFromIntInfo ii getDpFromIntInfo (WithIoI ii _) = getDpFromIntInfo ii getDpFromIntInfo _ = Nothing + getBeginRate :: InterestInfo -> IRate getBeginRate (Floater a _ _ _ _ _ _ ) = a getBeginRate (Fix a _ ) = a @@ -147,12 +144,13 @@ getBeginRate (RefRate a _ _ _ ) = a getBeginRate (CapRate a _ ) = getBeginRate a getBeginRate (FloorRate a _ ) = getBeginRate a getBeginRate (WithIoI a _) = getBeginRate a -getBeginRate InterestByYield {} = 0.0 + data StepUp = PassDateSpread Date Spread -- ^ add a spread on a date and effective afterwards | PassDateLadderSpread Date Spread RateReset -- ^ add a spread on the date pattern deriving (Show, Eq, Generic, Ord, Read) + data OriginalInfo = OriginalInfo { originBalance::Balance -- ^ issuance balance ,originDate::Date -- ^ issuance date @@ -165,6 +163,7 @@ type PlannedAmorSchedule = Ts -- ^ the way of principal due is calculated data BondType = Sequential -- ^ Pass through type tranche | PAC PlannedAmorSchedule -- ^ bond with schedule amortization + | AmtByPeriod (PerCurve Balance) -- ^ principal due by period | PacAnchor PlannedAmorSchedule [BondName] -- ^ pay till schdule balance if bonds from bond names has oustanding balance, if other bonds are paid off ,then pay oustanding balance | Lockout Date -- ^ No principal due till date | Z -- ^ Z tranche @@ -173,8 +172,7 @@ data BondType = Sequential -- ^ Pass through typ -- TODO: for multi int bond, should origin rate be a list of rates? --- : sofar remain orginate rate as a single rate for multi int bond - +-- : so far remain orginate rate as a single rate for multi int bond data Bond = Bond { bndName :: String ,bndType :: BondType -- ^ bond type ,which describe the how principal due was calculated @@ -194,22 +192,22 @@ data Bond = Bond { } | MultiIntBond { bndName :: String - ,bndType :: BondType -- ^ bond type ,which describe the how principal due was calculated - ,bndOriginInfo :: OriginalInfo -- ^ fact data on origination + ,bndType :: BondType -- ^ bond type ,which describe the how principal due was calculated + ,bndOriginInfo :: OriginalInfo -- ^ fact data on origination ,bndInterestInfos :: [InterestInfo] -- ^ interest info which used to update interest rate ,bndStepUps :: Maybe [StepUp] -- ^ step up which update interest rate -- status - ,bndBalance :: Balance -- ^ current balance + ,bndBalance :: Balance -- ^ current balance ,bndRates :: [IRate] -- ^ current rate - ,bndDuePrin :: Balance -- ^ principal due for current period + ,bndDuePrin :: Balance -- ^ principal due for current period ,bndDueInts :: [Balance] -- ^ interest due ,bndDueIntOverInts :: [Balance] -- ^ IoI - ,bndDueIntDates :: Maybe [Date] -- ^ last interest due calc date + ,bndDueIntDate :: Maybe Date -- ^ last interest due calc date ,bndLastIntPays :: Maybe [Date] -- ^ last interest pay date - ,bndLastPrinPay :: Maybe Date -- ^ last principal pay date - ,bndStmt :: Maybe S.Statement -- ^ transaction history + ,bndLastPrinPay :: Maybe Date -- ^ last principal pay date + ,bndStmt :: Maybe S.Statement -- ^ transaction history } - | BondGroup (Map.Map String Bond) -- ^ bond group + | BondGroup (Map.Map String Bond) -- ^ bond group deriving (Show, Eq, Generic, Ord, Read) @@ -224,7 +222,7 @@ bndTxns = lens getter setter bondCashflow :: Bond -> ([Date], [Amount]) bondCashflow b = - let t = (S.getAllTxns b) + let t = S.getAllTxns b in (S.getDate <$> t, S.getTxnAmt <$> t) @@ -337,37 +335,47 @@ payPrin d amt bnd = bnd {bndDuePrin =newDue, bndBalance = newBal , bndStmt=newSt r = getCurRate bnd newStmt = S.appendStmt (BondTxn d newBal 0 amt r amt dueInt dueIoI Nothing (S.PayPrin [bn] )) stmt -writeOff :: Date -> Amount -> Bond -> Bond -writeOff d 0 b = b -writeOff d amt bnd = bnd {bndBalance = newBal , bndStmt=newStmt} - where - newBal = (bndBalance bnd) - amt + +writeOff :: Date -> Amount -> Bond -> Either String Bond +writeOff d 0 b = Right b +writeOff d amt _bnd + | bndBalance _bnd < amt = Left $ "Insufficient balance to write off "++ show amt ++ show " bond name "++ show (bndName _bnd) + | otherwise = + let + bnd = accrueInt d _bnd + newBal = bndBalance bnd - amt dueIoI = getDueIntOverInt bnd dueInt = getDueInt bnd bn = bndName bnd stmt = bndStmt bnd newStmt = S.appendStmt (BondTxn d newBal 0 0 0 0 dueInt dueIoI Nothing (S.WriteOff bn amt )) stmt + in + Right $ bnd {bndBalance = newBal , bndStmt=newStmt} +-- TODO: should increase the original balance of the bond? fundWith :: Date -> Amount -> Bond -> Bond fundWith d 0 b = b -fundWith d amt bnd - = bnd {bndBalance = newBal, bndStmt=newStmt } +fundWith d amt _bnd = bnd {bndBalance = newBal, bndStmt=newStmt } where + bnd = accrueInt d _bnd dueIoI = getDueIntOverInt bnd dueInt = getDueInt bnd bn = bndName bnd stmt = bndStmt bnd - newBal = (bndBalance bnd) - amt + newBal = (bndBalance bnd) + amt newStmt = S.appendStmt (BondTxn d newBal 0 (negate amt) 0 0 dueInt dueIoI Nothing (S.FundWith bn amt )) stmt --- TODO: add how to handle different rate for IOI +-- ^ get interest rate for due interest getIoI :: InterestInfo -> IRate -> IRate +-- ^ inflate interest rate by pct over current rate getIoI (WithIoI _ (OverCurrRateBy r)) rate = rate * (1+ fromRational r) +-- ^ inflate interest rate by adding a fix spread getIoI (WithIoI _ (OverFixSpread r)) rate = rate + r +-- ^ no inflation,just use current bond's rate getIoI _ rate = rate - +-- ^ accure interest to a bond, update the due interest and due IoI of the bond accrueInt :: Date -> Bond -> Bond accrueInt d b@Bond{bndInterestInfo = ii,bndDueIntDate = mDueIntDate, bndDueInt= dueInt , bndDueIntOverInt = dueIoI, bndRate = r, bndBalance = bal} @@ -386,19 +394,16 @@ accrueInt d b@Bond{bndInterestInfo = ii,bndDueIntDate = mDueIntDate, bndDueInt= Nothing -> getOriginDate b --- TODO: HOW to accrue a single index ? -accrueInt d b@MultiIntBond{bndInterestInfos = iis, bndDueIntDates = mDueIntDates +-- accure all the index +accrueInt d b@MultiIntBond{bndInterestInfos = iis, bndDueIntDate = mDueIntDate , bndDueInts = dueInts, bndDueIntOverInts = dueIoIs , bndRates = rs, bndBalance = bal} - | all (==d) beginDates = b + | beginDate == d = b | otherwise = let l = length iis -- `debug` ("bond Name>>> "++ show (bndName b)) daycounts = (fromMaybe DC_ACT_365F) . getDayCountFromInfo <$> iis - beginDates = case mDueIntDates of - Just ds -> ds - Nothing -> getOriginDate b <$ [1..l] - periods = zipWith3 yearCountFraction daycounts beginDates (repeat d) -- `debug` ((bndName b) ++" date"++ show d++"daycounts"++show daycounts++"beginDates "++show beginDates++ show "end dates"++ show d) + periods = zipWith3 yearCountFraction daycounts (replicate l beginDate) (repeat d) -- `debug` ((bndName b) ++" date"++ show d++"daycounts"++show daycounts++"beginDates "++show beginDates++ show "end dates"++ show d) newDues = zipWith3 (\r p due -> (mulBR (mulBIR bal r) p) + due) rs periods dueInts -- `debug` ((bndName b) ++" date"++ show d++"rs"++show rs++"periods "++show periods++">>"++show dueInts) newIoiDues = zipWith5 (\r p due dueIoI ii -> (mulBR (mulBIR due (getIoI ii r)) p) + dueIoI) @@ -408,21 +413,26 @@ accrueInt d b@MultiIntBond{bndInterestInfos = iis, bndDueIntDates = mDueIntDates dueIoIs iis in - b {bndDueInts = newDues, bndDueIntOverInts = newIoiDues, bndDueIntDates = Just (replicate l d) } + b {bndDueInts = newDues, bndDueIntOverInts = newIoiDues, bndDueIntDate = Just d } where l = length iis - beginDates = case mDueIntDates of + beginDate = case mDueIntDate of Just ds -> ds - Nothing -> (getOriginDate b) <$ [1..l] + Nothing -> getOriginDate b accrueInt d (BondGroup bMap) = BondGroup $ accrueInt d <$> bMap - --- ^ TODO WAL for bond group calcWalBond :: Date -> Bond -> Rational calcWalBond d b@Bond{bndStmt = Nothing} = 0.0 calcWalBond d b@MultiIntBond{bndStmt = Nothing} = 0.0 +calcWalBond d (BondGroup bMap) + = let + bndWal = calcWalBond d <$> Map.elems bMap + bndBals = toRational . getCurBalance <$> Map.elems bMap + in + weightedBy bndBals bndWal + calcWalBond d b = let txns = cutBy Exc Future d $ S.getAllTxns b @@ -443,36 +453,7 @@ getTxnRate :: Txn -> IRate getTxnRate (BondTxn _ _ _ _ r _ _ _ _ _) = r getTxnRate _ = 0.0 --- ^TODO to be tested -calcAccrueInt :: Date -> Bond -> Balance -calcAccrueInt d bnd@(BondGroup bMap) = sum $ calcAccrueInt d <$> Map.elems bMap -calcAccrueInt d bnd@(Bond {bndStmt = mstmt, bndRate = r, bndBalance = bal}) - | isNothing mstmt = IR.calcInt bal (getOriginDate bnd) d r DC_ACT_365F - | d <= getOriginDate bnd = 0 - | isJust mstmt = - let - txns = S.getAllTxns bnd - ds = S.getDate <$> txns - in - case (S.getTxnAsOf txns d, elem d ds) of - (_ , True) -> 0 - (Nothing,_) -> IR.calcInt bal (getOriginDate bnd) d r DC_ACT_365F -- `debug` (">>> "++ show (getOriginDate bnd) ++ ">>> "++ show d++"bal"++show bal++"rate"++show r++"r"++ show ( IR.calcInt bal (getOriginDate bnd) d r DC_ACT_365F)++ ">>\n txns"++ show txns) - (Just txn,_) -> IR.calcInt (S.getTxnBalance txn) (S.getDate txn) d r DC_ACT_365F -- `debug` ("Accrue Int"++show d++">>"++show (S.getDate txn)++ ">>"++show (S.getTxnBalance txn)++"Rate"++show r) - -calcAccrueInt d bnd@(MultiIntBond {bndStmt = mstmt, bndRates = rs, bndBalance = bal}) - | isNothing mstmt = IR.calcInt bal (getOriginDate bnd) d (sum rs) DC_ACT_365F - | d <= getOriginDate bnd = 0 - | isJust mstmt = - let - txns = S.getAllTxns bnd - ds = S.getDate <$> txns - in - case (S.getTxnAsOf txns d, elem d ds) of - (_, True) -> 0 - (Nothing,_) -> IR.calcInt bal (getOriginDate bnd) d (sum rs) DC_ACT_365F - (Just txn,_) -> IR.calcInt (S.getTxnBalance txn) (S.getDate txn) d (getTxnRate txn) DC_ACT_365F - - +-- ^ get present value of a bond priceBond :: Date -> Ts -> Bond -> PriceResult priceBond d rc b@(Bond _ _ _ _ _ _ _ _ _ _ _ _ _ Nothing ) = PriceResult 0 0 0 0 0 0 [] priceBond d rc b@(MultiIntBond _ _ _ _ _ _ _ _ _ _ _ _ _ Nothing ) = PriceResult 0 0 0 0 0 0 [] @@ -484,24 +465,10 @@ priceBond d rc bnd cutoffBalance = case S.getTxnAsOf txns d of Nothing -> (S.getTxnBegBalance . head) txns Just _txn -> S.getTxnBegBalance _txn - accruedInt = calcAccrueInt d bnd + accruedInt = getTotalDueInt (accrueInt d bnd) wal = calcWalBond d bnd - duration = calcDuration d (zip futureCfDates futureCfFlow) rc - convexity = let - b = (foldr (\x acc -> - let - _t = yearCountFraction DC_ACT_365F d (S.getDate x) -- `debug` ("calc _T"++show d++">>"++show (S.getTxnDate x)) - _t2 = _t * _t + _t -- `debug` ("T->"++show _t) - _cash_date = S.getDate x - _yield = getValByDate rc Exc _cash_date - _y = (1+ _yield) * (1+ _yield) -- `debug` ("yield->"++ show _yield++"By date"++show d) - _x = ((mulBR (pv rc d _cash_date (S.getTxnAmt x)) _t2) / (fromRational _y)) -- `debug` ("PRICING -E") -- `debug` ("PV:->"++show (pv rc d (S.getTxnDate x) (S.getTxnAmt x))++"Y->"++ show _y++"T2-->"++ show _t2) - in - _x + acc) - 0 - futureCfs) -- `debug` ("PRICING VALUE"++ show presentValue) - in - b/presentValue -- `debug` "PRICING -D" -- `debug` ("B->"++show b++"PV"++show presentValue) + duration = calcDuration DC_ACT_365F d (zip futureCfDates futureCfFlow) rc + convexity = calcConvexity DC_ACT_365F d (zip futureCfDates futureCfFlow) rc in PriceResult presentValue (fromRational (100* (safeDivide' presentValue obal))) (realToFrac wal) (realToFrac duration) (realToFrac convexity) accruedInt futureCfs-- `debug` ("Obal->"++ show obal++"Rate>>"++ show (bndRate b)) where @@ -515,19 +482,6 @@ priceBond d rc bnd od = getOriginDate bnd --- ^ backout interest due for a Yield Maintainace type bond --- ^ TODO: need to handle MuitIntBond here -backoutDueIntByYield :: Date -> Bond -> Balance -backoutDueIntByYield d b@(Bond _ _ (OriginalInfo obal odate _ _) (InterestByYield y) _ currentBalance _ _ _ _ _ _ _ stmt) - = projFv - fvs - currentBalance -- `debug` ("Date"++ show d ++"FV->"++show projFv++">>"++show fvs++">>cb"++show currentBalance) - where - projFv = fv2 y odate d obal - fvs = sum $ [ fv2 y cfDate d cfAmt | (cfDate,cfAmt) <- cashflows ] -- `debug` (show d ++ ":CFS"++ show cashflows) - cashflows = case stmt of - Just (S.Statement txns) -> [ ((S.getDate txn),(S.getTxnAmt txn)) | txn <- txns ] -- `debug` (show d ++":TXNS"++ show txns) - Nothing -> [] - - weightAverageBalance :: Date -> Date -> Bond -> Balance weightAverageBalance sd ed b@(Bond _ _ (OriginalInfo ob bd _ _ ) _ _ currentBalance _ _ _ _ _ _ _ Nothing) = mulBR currentBalance (yearCountFraction DC_ACT_365F (max bd sd) ed) @@ -550,53 +504,37 @@ weightAverageBalance sd ed b@(MultiIntBond _ _ (OriginalInfo ob bd _ _ ) _ _ cu weightAverageBalance sd ed bg@(BondGroup bMap) = sum $ weightAverageBalance sd ed <$> Map.elems bMap -- `debug` (">>>"++ show (weightAverageBalance sd ed <$> Map.elems bMap)) -calcZspread :: (Rational,Date) -> Int -> (Float, (Rational,Rational),Rational) -> Bond -> Ts -> Spread -calcZspread _ _ _ b@Bond{bndStmt = Nothing} _ = error "No Cashflow for bond" -calcZspread _ _ _ b@MultiIntBond{bndStmt = Nothing} _ = error "No Cashflow for bond" -calcZspread (tradePrice,priceDay) count (level ,(lastSpd,lastSpd2),spd) b riskFreeCurve - | count >= 10000 = fromRational spd -- error "Failed to find Z spread with 10000 times try" - | otherwise = + +tryCalcZspread :: Rational -> Balance -> Date -> [(Date,Balance)] -> Ts -> Double -> Double +tryCalcZspread tradePrice originBalance priceDay futureCfs riskFreeCurve spread + = let + pvCurve = shiftTsByAmt riskFreeCurve (fromRational (toRational spread)) + pvs = [ pv pvCurve priceDay _d _amt | (_d, _amt) <- futureCfs ] + newPrice = 100 * sum pvs + faceVal = fromRational $ divideBB newPrice originBalance + in + faceVal - fromRational tradePrice + + +calcZspread :: (Rational,Date) -> Bond -> Ts -> Either String Spread +calcZspread _ b@Bond{bndStmt = Nothing} _ = Left "No Cashflow for bond" +calcZspread _ b@MultiIntBond{bndStmt = Nothing} _ = Left "No Cashflow for bond" +calcZspread (tradePrice,priceDay) b riskFreeCurve = let txns = S.getAllTxns b bInfo = bndOriginInfo b (_,futureTxns) = splitByDate txns priceDay EqToRight - cashflow = S.getTxnAmt <$> futureTxns ds = S.getDate <$> futureTxns oBalance = originBalance bInfo - - pvCurve = shiftTsByAmt riskFreeCurve spd -- `debug` ("Shfiting using spd"++ show (fromRational spd)) - pvs = [ pv pvCurve priceDay _d _amt | (_d, _amt) <- zip ds cashflow ] -- `debug` (" using pv curve"++ show pvCurve) - newPrice = 100 * sum pvs -- `debug` ("PVS->>"++ show pvs) - pricingFaceVal = toRational $ newPrice / oBalance -- `debug` ("new price"++ show newPrice) - gap = (pricingFaceVal - tradePrice) -- `debug` ("Face val"++show pricingFaceVal++"T price"++show tradePrice) - newSpd = case [gap ==0 ,gap > 0, spd > 0] of - [True,_,_] -> spd - [_,True,_] -> spd + f -- `debug` ("1 -> "++ show f) - [_,False,_] -> spd - f -- `debug` ("3 -> "++ show f) - where - f = let - thresholds = toRational <$> (level *) <$> [50,20,10,5,2,0.1,0.05,0.01,0.005] - shiftPcts = (level *) <$> [0.5,0.2,0.1,0.05,0.02,0.01,0.005,0.001,0.0005] - in - case find (\(a,b) -> a < abs(toRational gap)) (zip thresholds shiftPcts ) of - Just (_,v) -> toRational v -- `debug` ("shifting ->"++ show v) - Nothing -> toRational (level * 0.00001) -- `debug` ("shifting-> <> 0.00005") - - newLevel = case [abs newSpd < 0.0001 - ,abs(newSpd-lastSpd)<0.000001 - ,abs(newSpd-lastSpd2)<0.000001] of - [True,_,_] -> level * 0.5 - [_,True,_] -> level * 0.5 - [_,_,True] -> level * 0.5 - _ -> level - in - if abs(pricingFaceVal - tradePrice) <= 0.01 then - fromRational spd -- `debug` ("Curve -> "++show pvCurve) - else - calcZspread (tradePrice,priceDay) (succ count) (newLevel, (spd, lastSpd), newSpd) b riskFreeCurve -- `debug` ("new price"++ show pricingFaceVal++"trade price"++ show tradePrice++ "new spd"++ show (fromRational newSpd)) - - + itertimes = 500 + def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.00001 } + in + case ridders def (0.0001,100) (tryCalcZspread tradePrice oBalance priceDay (zip ds cashflow) riskFreeCurve) of + Root r -> Right (fromRational (toRational r)) + _ -> Left $ "Failed to find Z spread with "++ show itertimes ++ " times try" + +-- ^ get total funded balance (from transaction) of a bond totalFundedBalance :: Bond -> Balance totalFundedBalance (BondGroup bMap) = sum $ totalFundedBalance <$> Map.elems bMap totalFundedBalance b @@ -639,7 +577,6 @@ buildStepUpDates b@Bond{bndStepUp = mSt } sd ed buildStepUpDates b@MultiIntBond{bndStepUps = mSt } sd ed = case mSt of Nothing -> [] - -- TODO: perf: sort and distinct Just sts -> Set.toList $ Set.fromList $ concat $ @@ -649,33 +586,6 @@ buildStepUpDates b@MultiIntBond{bndStepUps = mSt } sd ed (PassDateSpread d _) -> [d] ) <$> sts --- buildRateResetDates b@MultiIntBond{bndInterestInfo = ii,bndStepUp = mSt } sd ed - - --- scaleBond :: Rate -> Bond -> Bond --- scaleBond r (BondGroup bMap) = BondGroup $ Map.map (scaleBond r) bMap --- scaleBond r b@Bond{ bndOriginInfo = oi, bndInterestInfo = iinfo, bndStmt = mstmt --- , bndBalance = bal, bndDuePrin = dp, bndDueInt = di, bndDueIntDate = did --- , bndLastIntPay = lip, bndLastPrinPay = lpp --- , bndType = bt} --- = b { --- bndType = scaleBndType r bt --- ,bndOriginInfo = scaleBndOriginInfo r oi --- ,bndBalance = mulBR bal r --- ,bndDuePrin = mulBR dp r --- ,bndDueInt = mulBR di r --- ,bndStmt = scaleStmt r mstmt --- } --- where --- scaleBndType r (PAC ts) = let --- vs = (flip mulBR r . fromRational <$> getTsVals ts) --- ds = getTsDates ts --- in --- PAC $ BalanceCurve [ TsPoint d v | (d,v) <- zip ds vs] --- scaleBndType r _bt = _bt --- scaleBndOriginInfo r oi@OriginalInfo{originBalance = ob} = oi {originBalance = mulBR ob r} --- scaleStmt r Nothing = Nothing --- scaleStmt r (Just (S.Statement txns)) = Just (S.Statement (S.scaleTxn r <$> txns)) instance S.QueryByComment Bond where queryStmt Bond{bndStmt = Nothing} tc = [] @@ -707,12 +617,13 @@ instance Liable Bond where (toRational . getCurBalance <$> Map.elems bMap) (toRational . getCurRate <$> Map.elems bMap) - getOriginBalance b = originBalance $ bndOriginInfo b getOriginBalance (BondGroup bMap) = sum $ getOriginBalance <$> Map.elems bMap + getOriginBalance b = originBalance $ bndOriginInfo b getOriginDate b = originDate $ bndOriginInfo b + -- ^ get due int of a bond getDueInt b@Bond{bndDueInt=di} = di getDueInt MultiIntBond{bndDueInts=dis} = sum dis getDueInt (BondGroup bMap) = sum $ getDueInt <$> Map.elems bMap @@ -721,10 +632,12 @@ instance Liable Bond where getDueIntOverIntAt MultiIntBond{bndDueIntOverInts=diois} idx = diois !! idx getTotalDueIntAt b idx = getDueIntAt b idx + getDueIntOverIntAt b idx + -- ^ get due IoI of a bond getDueIntOverInt b@Bond{bndDueIntOverInt=dioi} = dioi getDueIntOverInt MultiIntBond{bndDueIntOverInts=diois} = sum diois getDueIntOverInt (BondGroup bMap) = sum $ getDueIntOverInt <$> Map.elems bMap + -- ^ get total due interest of a bond (both due int and due IoI) getTotalDueInt b@Bond{bndDueInt=di,bndDueIntOverInt=dioi} = di + dioi getTotalDueInt MultiIntBond{bndDueInts=dis,bndDueIntOverInts=diois} = sum dis + sum diois getTotalDueInt (BondGroup bMap) = sum $ getTotalDueInt <$> Map.elems bMap diff --git a/src/Lib.hs b/src/Lib.hs index af9686e0..48f4edf6 100644 --- a/src/Lib.hs +++ b/src/Lib.hs @@ -10,7 +10,7 @@ module Lib ,afterNPeriod,Ts(..),periodsBetween ,periodRateFromAnnualRate ,Floor,Cap,TsPoint(..) - ,toDate,toDates,genDates,nextDate,isTsEmpty + ,toDate,toDates,genDates,nextDate ,getValOnByDate,sumValTs,subTsBetweenDates,splitTsByDate ,paySeqLiabilitiesAmt,getIntervalDays,getIntervalFactors ,zipWith8,zipWith9,zipWith10,zipWith11,zipWith12 @@ -42,19 +42,6 @@ debug = flip trace - -annualRateToPeriodRate :: Period -> Float -> Float -annualRateToPeriodRate p annualRate = - 1 - (1 - annualRate ) ** n - where - n = case p of - Monthly -> 1/12 - Quarterly -> 1/4 - SemiAnnually -> 1/2 - Annually -> 1.0 - Daily -> 1 / 365 - Weekly -> 1 / 52.143 - periodRateFromAnnualRate :: Period -> IRate -> IRate periodRateFromAnnualRate Annually annual_rate = annual_rate periodRateFromAnnualRate Monthly annual_rate = annual_rate / 12 @@ -163,7 +150,7 @@ splitTsByDate (BalanceCurve ds) d where (l,r) = splitAt idx ds -subTsBetweenDates :: Ts -> Maybe T.Day -> Maybe T.Day -> Ts +subTsBetweenDates :: Ts -> Maybe Date -> Maybe Date -> Ts subTsBetweenDates (BalanceCurve vs) (Just sd) (Just ed) = BalanceCurve $ filter(\(TsPoint x _) -> (x > sd) && (x < ed) ) vs subTsBetweenDates (BalanceCurve vs) Nothing (Just ed) @@ -183,28 +170,27 @@ toDates ds = toDate <$> ds zipWith8 :: (a->b->c->d->e->f->g->h->i) -> [a]->[b]->[c]->[d]->[e]->[f]->[g]->[h]->[i] zipWith8 z (a:as) (b:bs) (c:cs) (d:ds) (e:es) (f:fs) (g:gs) (h:hs) - = z a b c d e f g h : zipWith8 z as bs cs ds es fs gs hs + = z a b c d e f g h : zipWith8 z as bs cs ds es fs gs hs zipWith8 _ _ _ _ _ _ _ _ _ = [] zipWith9 :: (a->b->c->d->e->f->g->h->i->j) -> [a]->[b]->[c]->[d]->[e]->[f]->[g]->[h]->[i]->[j] zipWith9 z (a:as) (b:bs) (c:cs) (d:ds) (e:es) (f:fs) (g:gs) (h:hs) (j:js) - = z a b c d e f g h j : zipWith9 z as bs cs ds es fs gs hs js + = z a b c d e f g h j : zipWith9 z as bs cs ds es fs gs hs js zipWith9 _ _ _ _ _ _ _ _ _ _ = [] zipWith10 :: (a->b->c->d->e->f->g->h->i->j->k) -> [a]->[b]->[c]->[d]->[e]->[f]->[g]->[h]->[i]->[j]->[k] zipWith10 z (a:as) (b:bs) (c:cs) (d:ds) (e:es) (f:fs) (g:gs) (h:hs) (j:js) (k:ks) - = z a b c d e f g h j k: zipWith10 z as bs cs ds es fs gs hs js ks + = z a b c d e f g h j k: zipWith10 z as bs cs ds es fs gs hs js ks zipWith10 _ _ _ _ _ _ _ _ _ _ _ = [] zipWith11 :: (a->b->c->d->e->f->g->h->i->j->k->l) -> [a]->[b]->[c]->[d]->[e]->[f]->[g]->[h]->[i]->[j]->[k]->[l] zipWith11 z (a:as) (b:bs) (c:cs) (d:ds) (e:es) (f:fs) (g:gs) (h:hs) (j:js) (k:ks) (l:ls) - = z a b c d e f g h j k l: zipWith11 z as bs cs ds es fs gs hs js ks ls + = z a b c d e f g h j k l: zipWith11 z as bs cs ds es fs gs hs js ks ls zipWith11 _ _ _ _ _ _ _ _ _ _ _ _ = [] - zipWith12 :: (a->b->c->d->e->f->g->h->i->j->k->l->m) -> [a]->[b]->[c]->[d]->[e]->[f]->[g]->[h]->[i]->[j]->[k]->[l]->[m] zipWith12 z (a:as) (b:bs) (c:cs) (d:ds) (e:es) (f:fs) (g:gs) (h:hs) (j:js) (k:ks) (l:ls) (m:ms) - = z a b c d e f g h j k l m: zipWith12 z as bs cs ds es fs gs hs js ks ls ms + = z a b c d e f g h j k l m: zipWith12 z as bs cs ds es fs gs hs js ks ls ms zipWith12 _ _ _ _ _ _ _ _ _ _ _ _ _ = [] @@ -252,9 +238,4 @@ nextDate d p Quarterly -> 3 SemiAnnually -> 6 Annually -> 12 - _ -> 0 - -isTsEmpty :: Ts -> Bool -isTsEmpty (FloatCurve []) = True -isTsEmpty (RatioCurve []) = True -isTsEmpty _ = False + _ -> 0 \ No newline at end of file diff --git a/src/Pool.hs b/src/Pool.hs index cfc2b1ad..ea4ee533 100644 --- a/src/Pool.hs +++ b/src/Pool.hs @@ -74,11 +74,11 @@ poolFutureTxn :: Asset a => Lens' (Pool a) [CF.TsRow] poolFutureTxn = lens getter setter where getter p = case futureCf p of - Nothing -> []::[CF.TsRow] - Just (CF.CashFlowFrame _ txns) -> txns + Nothing -> []::[CF.TsRow] + Just (CF.CashFlowFrame _ txns) -> txns setter p trs = case futureCf p of - Nothing -> p {futureCf = Just (CF.CashFlowFrame (0,toDate "19000101",Nothing) trs)} --TODO fix this - Just (CF.CashFlowFrame st _) -> p {futureCf = Just (CF.CashFlowFrame st trs)} + Nothing -> p {futureCf = Just (CF.CashFlowFrame (0,toDate "19000101",Nothing) trs)} --TODO fix this + Just (CF.CashFlowFrame st _) -> p {futureCf = Just (CF.CashFlowFrame st trs)} poolIssuanceStat :: Asset a => Lens' (Pool a) (Map.Map CutoffFields Balance) poolIssuanceStat = lens getter setter @@ -154,7 +154,7 @@ calcLiquidationAmount (BalanceFactor currentFactor defaultFactor ) pool d in case earlierTxns of [] -> 0 -- `debug` ("No pool Inflow") - _ -> (mulBR (CF.mflowBalance (last earlierTxns)) currentFactor) + (mulBR currentCumulativeDefaultBal defaultFactor) + _ -> (mulBR (view CF.tsRowBalance (last earlierTxns)) currentFactor) + (mulBR currentCumulativeDefaultBal defaultFactor) -- TODO need to check if missing last row diff --git a/src/Stmt.hs b/src/Stmt.hs index a0e880f5..2f345751 100644 --- a/src/Stmt.hs +++ b/src/Stmt.hs @@ -6,14 +6,14 @@ module Stmt (Statement(..) - ,getTxns,getTxnComment,getTxnAmt,toDate,getTxnPrincipal,getTxnAsOf,getTxnBalance - ,appendStmt,combineTxn,getTxnBegBalance,getDate,getDates - ,TxnComment(..),QueryByComment(..) - ,weightAvgBalanceByDates,weightAvgBalance,weightAvgBalance',sumTxn, consolTxn - ,getFlow,FlowDirection(..), aggByTxnComment,scaleByFactor - ,scaleTxn,isEmptyTxn, statementTxns, viewBalanceAsOf,filterTxn - ,HasStmt(..) - ,getAllTxns,hasEmptyTxn + ,getTxns,getTxnComment,getTxnAmt,toDate,getTxnPrincipal,getTxnAsOf,getTxnBalance + ,appendStmt,combineTxn,getTxnBegBalance,getDate,getDates + ,TxnComment(..),QueryByComment(..) + ,weightAvgBalanceByDates,weightAvgBalance,weightAvgBalance',sumTxn, consolTxn + ,getFlow,FlowDirection(..), aggByTxnComment,scaleByFactor + ,scaleTxn,isEmptyTxn, statementTxns, viewBalanceAsOf,filterTxn + ,HasStmt(..),Txn(..) + ,getAllTxns,hasEmptyTxn ) where @@ -219,30 +219,30 @@ getFlow comment = PayPrin _ -> Outflow PayGroupPrin _ -> Outflow PayGroupInt _ -> Outflow - WriteOff _ _ -> Noneflow - FundWith _ _ -> Inflow PayPrinResidual _ -> Outflow PayFee _ -> Outflow SeqPayFee _ -> Outflow PayFeeYield _ -> Outflow + LiquidationRepay _ -> Outflow + SwapOutSettle _ -> Outflow + PurchaseAsset _ _-> Outflow Transfer _ _ -> Interflow TransferBy _ _ _ -> Interflow + FundWith _ _ -> Inflow PoolInflow _ _ -> Inflow LiquidationProceeds _ -> Inflow LiquidationSupport _ -> Inflow + BankInt -> Inflow + SwapInSettle _ -> Inflow + IssuanceProceeds _ -> Inflow LiquidationDraw -> Noneflow - LiquidationRepay _ -> Outflow LiquidationSupportInt _ _ -> Noneflow - BankInt -> Inflow + WriteOff _ _ -> Noneflow SupportDraw -> Noneflow Empty -> Noneflow Tag _ -> Noneflow UsingDS _ -> Noneflow SwapAccrue -> Noneflow - SwapInSettle _ -> Inflow - SwapOutSettle _ -> Outflow - PurchaseAsset _ _-> Outflow - IssuanceProceeds _ -> Inflow TxnDirection _ -> Noneflow BookLedgerBy _ _ -> Noneflow TxnComments cmts -> --TODO the direction of combine txns @@ -257,18 +257,15 @@ getFlow comment = Noneflow _ -> error ("Missing in GetFlow >> "++ show comment) - +-- ^ filter transaction by apply a filter function on txn comment filterTxn :: (TxnComment -> Bool) -> [Txn] -> [Txn] -filterTxn f txns = filter (\t -> f (getTxnComment t) ) txns +filterTxn f = filter (f . getTxnComment) instance Ord Txn where compare :: Txn -> Txn -> Ordering compare (BondTxn d1 _ _ _ _ _ _ _ _ _) (BondTxn d2 _ _ _ _ _ _ _ _ _) = compare d1 d2 compare (AccTxn d1 _ _ _ ) (AccTxn d2 _ _ _ ) = compare d1 d2 --- instance Eq Txn where --- (BondTxn d1 _ _ _ _ _ _ _) == (BondTxn d2 _ _ _ _ _ _ _) = d1 == d2 - instance TimeSeries Txn where getDate (BondTxn t _ _ _ _ _ _ _ _ _ ) = t getDate (AccTxn t _ _ _ ) = t @@ -278,11 +275,15 @@ instance TimeSeries Txn where getDate (EntryTxn t _ _ _) = t class QueryByComment a where + queryStmt :: a -> TxnComment -> [Txn] + queryStmtAsOf :: a -> Date -> TxnComment -> [Txn] queryStmtAsOf a d tc = [ txn | txn <- queryStmt a tc, getDate txn <= d] + queryTxnAmt :: a -> TxnComment -> Balance queryTxnAmt a tc = sum $ map getTxnAmt $ queryStmt a tc + queryTxnAmtAsOf :: a -> Date -> TxnComment -> Balance queryTxnAmtAsOf a d tc = sum $ getTxnAmt <$> queryStmtAsOf a d tc diff --git a/src/Types.hs b/src/Types.hs index 23b46ce3..f363d412 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -11,6 +11,7 @@ module Types ,DatePattern(..) ,BondName,BondNames,FeeName,FeeNames,AccName,AccNames,AccountName ,Ts(..),TsPoint(..),PoolSource(..) + ,PerPoint(..),PerCurve(..),getValFromPerCurve ,Period(..), Threshold(..) ,RangeType(..),CutoffType(..),DealStatus(..) ,Balance,Index(..) @@ -28,7 +29,7 @@ module Types ,DealName,lookupIntervalTable,CutoffFields(..),PriceResult(..) ,DueInt,DuePremium, DueIoI,DateVector,DealStats(..) ,PricingMethod(..),CustomDataType(..),ResultComponent(..),DealStatType(..) - ,ActionWhen(..) + ,ActionWhen(..),DealStatFields(..) ,getDealStatType,getPriceValue,preHasTrigger ,MyRatio,HowToPay(..),ApplyRange(..) ) @@ -47,10 +48,11 @@ import Text.Regex.PCRE import GHC.Generics import Language.Haskell.TH -import Text.Read (readMaybe) +import Text.Read (readMaybe, get) import Data.Aeson (ToJSON, toJSON, Value(String)) import Data.Ratio (Ratio, numerator, denominator) import Data.Text (pack) +import Control.DeepSeq (NFData,rnf) import Data.Scientific (fromRationalRepetend,formatScientific, Scientific,FPFormat(Fixed)) @@ -61,9 +63,11 @@ import Data.Fixed hiding (Ratio) import Data.Ix -import Data.List (intercalate, findIndex) +import Data.List (intercalate, findIndex, find) -- import Cashflow (CashFlowFrame) +-- import Web.Hyperbole hiding (All,Fixed) + import Debug.Trace -- import qualified Cashflow as CF debug = flip trace @@ -120,7 +124,7 @@ type Lag = Int type Valuation = Centi type PerFace = Micro type WAL = Centi -type Duration = Balance +type Duration = Micro type Convexity = Micro type Yield = Micro type AccruedInterest = Centi @@ -268,6 +272,63 @@ data PoolSource = CollectedInterest -- ^ interest data TsPoint a = TsPoint Date a deriving (Show,Eq,Read,Generic) +data PerPoint a = PerPoint Int a + deriving (Show,Eq,Read,Generic) + +data PerCurve a = CurrentVal [PerPoint a] + | WithTrailVal [PerPoint a] + deriving (Show,Eq,Read,Generic,Ord) + +getValFromPerCurve :: PerCurve a -> DateDirection -> CutoffType -> Int -> Maybe a +getValFromPerCurve (WithTrailVal []) _ _ _ = Nothing +getValFromPerCurve (CurrentVal []) _ _ _ = Nothing + +getValFromPerCurve (CurrentVal (v:vs)) Future p i + = let + cmp = case p of + Inc -> (>=) + Exc -> (>) + in + if cmp (getIdxFromPerPoint v) i then + Just $ getValFromPerPoint v + else + getValFromPerCurve (CurrentVal vs) Future p i + +getValFromPerCurve (CurrentVal vs) Past p i + = let + cmp = case p of + Inc -> (<=) + Exc -> (<) + ps = reverse vs + in + case find (\x -> cmp (getIdxFromPerPoint x) i) ps of + Just rs -> Just $ getValFromPerPoint rs + Nothing -> Nothing + + +getValFromPerCurve (WithTrailVal _ps) dr p i + = let + ps = case dr of + Future -> _ps + Past -> reverse _ps + cmp = case p of + Inc -> (>=) + Exc -> (>) + in + case find (\x -> cmp (getIdxFromPerPoint x) i) ps of + Nothing -> Just $ getValFromPerPoint (last ps) + Just rs -> Just $ getValFromPerPoint rs + +getIdxFromPerPoint :: PerPoint a -> Int +getIdxFromPerPoint (PerPoint i _) = i + +getValFromPerPoint :: PerPoint a -> a +getValFromPerPoint (PerPoint _ v) = v + + +instance Ord a => Ord (PerPoint a) where + compare (PerPoint i _) (PerPoint j _) = compare i j + data RangeType = II -- ^ include both start and end date | IE -- ^ include start date ,but not end date | EI -- ^ exclude start date but include end date @@ -390,7 +451,7 @@ data DealStatus = DealAccelerated (Maybe Date) -- ^ Deal is accelerated sta | Ended -- ^ Deal is marked as closed deriving (Show,Ord,Eq,Read, Generic) --- ^ different pricing methods +-- ^ pricing methods for assets data PricingMethod = BalanceFactor Rate Rate -- ^ [balance] to be multiply with rate1 and rate2 if status of asset is "performing" or "defaulted" | BalanceFactor2 Rate Rate Rate -- ^ [balance] by performing/delinq/default factor | DefaultedBalance Rate -- ^ [balance] only liquidate defaulted balance @@ -400,7 +461,14 @@ data PricingMethod = BalanceFactor Rate Rate -- ^ [balance] to be multi | PvWal Ts | PvByRef DealStats -- ^ [CF] Pricing cashflow with a ref rate | Custom Rate -- ^ custom amount - deriving (Show, Eq ,Generic, Read,Ord) + deriving (Show, Eq ,Generic, Read, Ord) + +-- ^ pricing methods for bonds +data BondPricingMethod = BondBalanceFactor Rate + | PvBondByRate Rate + | PvBondByCurve Ts + deriving (Show, Eq ,Generic, Read, Ord) + -- ^ condition which can be evaluated to a boolean value data Pre = IfZero DealStats @@ -487,6 +555,11 @@ data Txn = BondTxn Date Balance Interest Principal IRate Cash DueInt DueIoI (May | TrgTxn Date Bool TxnComment deriving (Show, Generic, Eq, Read) + +data DealStatFields = PoolCollectedPeriod + | BondPaidPeriod + deriving (Generic, Eq, Ord, Show, Read) + -- ^ different types of deal stats data DealStats = CurrentBondBalance | CurrentPoolBalance (Maybe [PoolId]) @@ -532,7 +605,7 @@ data DealStats = CurrentBondBalance | BondBalanceGapAt Date BondName | BondDuePrin [BondName] | BondReturn BondName Balance [TsPoint Amount] - | FeePaidAt Date FeeName + | FeePaidAmt [FeeName] | FeeTxnAmt [FeeName] (Maybe TxnComment) | BondTxnAmt [BondName] (Maybe TxnComment) | AccTxnAmt [AccName] (Maybe TxnComment) @@ -562,6 +635,7 @@ data DealStats = CurrentBondBalance | WeightedAvgOriginalPoolBalance Date Date (Maybe [PoolId]) | WeightedAvgOriginalBondBalance Date Date [BondName] | CustomData String Date + | DealStatBalance DealStatFields -- analytical query | AmountRequiredForTargetIRR Double BondName -- integer type @@ -569,6 +643,7 @@ data DealStats = CurrentBondBalance | FutureCurrentPoolBorrowerNum Date (Maybe [PoolId]) | ProjCollectPeriodNum | MonthsTillMaturity BondName + | DealStatInt DealStatFields -- boolean type | TestRate DealStats Cmp Micro | TestAny Bool [DealStats] @@ -580,6 +655,7 @@ data DealStats = CurrentBondBalance | IsOutstanding [BondName] | HasPassedMaturity [BondName] | TriggersStatus DealCycle String + | DealStatBool DealStatFields -- rate type | PoolWaRate (Maybe PoolId) | BondRate BondName @@ -592,6 +668,7 @@ data DealStats = CurrentBondBalance | CumulativePoolDefaultedRateTill Int (Maybe [PoolId]) | PoolFactor (Maybe [PoolId]) | BondWaRate [BondName] + | DealStatRate DealStatFields -- Compond type | Factor DealStats Rational | Multiply [DealStats] @@ -689,7 +766,7 @@ data CutoffFields = IssuanceBalance -- ^ pool issuance balance | HistoryFeePaid | AccruedInterest -- ^ accrued interest at closing | RuntimeCurrentPoolBalance -- ^ current pool balance - deriving (Show,Ord,Eq,Read,Generic) + deriving (Show,Ord,Eq,Read,Generic,NFData) data PriceResult = PriceResult Valuation PerFace WAL Duration Convexity AccruedInterest [Txn] @@ -774,8 +851,7 @@ instance TimeSeries (TsPoint a) where instance Ord a => Ord (TsPoint a) where compare (TsPoint d1 tv1) (TsPoint d2 tv2) = compare d1 d2 - - + -- compare (PoolPeriodPoint i1 tv1) (PoolPeriodPoint i2 tv2) = compare i1 i2 instance Show PoolId where show (PoolName n) = n @@ -800,6 +876,7 @@ instance (Read PoolId) where $(deriveJSON defaultOptions ''TsPoint) +$(deriveJSON defaultOptions ''PerPoint) $(deriveJSON defaultOptions ''Ts) $(deriveJSON defaultOptions ''Cmp) $(deriveJSON defaultOptions ''PoolSource) @@ -975,10 +1052,16 @@ getDealStatType (PoolWaRate _) = RtnRate getDealStatType (BondRate _) = RtnRate getDealStatType (DivideRatio {}) = RtnRate getDealStatType (AvgRatio {}) = RtnRate +getDealStatType (DealStatRate _) = RtnRate +getDealStatType (Avg dss) = RtnRate +getDealStatType (Divide ds1 ds2) = RtnRate +getDealStatType (Multiply _) = RtnRate +getDealStatType (Factor _ _) = RtnRate getDealStatType (CurrentPoolBorrowerNum _) = RtnInt getDealStatType (MonthsTillMaturity _) = RtnInt getDealStatType ProjCollectPeriodNum = RtnInt +getDealStatType (DealStatInt _) = RtnInt getDealStatType (IsMostSenior _ _) = RtnBool getDealStatType (TriggersStatus _ _)= RtnBool @@ -986,11 +1069,7 @@ getDealStatType (IsDealStatus _)= RtnBool getDealStatType TestRate {} = RtnBool getDealStatType (TestAny _ _) = RtnBool getDealStatType (TestAll _ _) = RtnBool - -getDealStatType (Avg dss) = RtnRate -getDealStatType (Divide ds1 ds2) = RtnRate -getDealStatType (Multiply _) = RtnRate -getDealStatType (Factor _ _) = RtnRate +getDealStatType (DealStatBool _) = RtnBool getDealStatType (Max dss) = getDealStatType (head dss) getDealStatType (Min dss) = getDealStatType (head dss) @@ -1009,9 +1088,7 @@ opts = defaultJSONKeyOptions -- { keyModifier = toLower } $(deriveJSON defaultOptions ''DealStatus) $(deriveJSON defaultOptions ''CutoffType) - - --- $(deriveJSON defaultOptions ''DateType) +$(deriveJSON defaultOptions ''DealStatFields) $(concat <$> traverse (deriveJSON defaultOptions) [''BookDirection, ''DealStats, ''PricingMethod, ''DealCycle, ''DateType, ''Period, ''DatePattern, ''Table, ''BalanceSheetReport, ''BookItem, ''CashflowReport, ''Txn] ) @@ -1052,6 +1129,7 @@ $(deriveJSON defaultOptions ''PriceResult) $(deriveJSON defaultOptions ''CutoffFields) $(deriveJSON defaultOptions ''HowToPay) +$(deriveJSON defaultOptions ''PerCurve) @@ -1097,4 +1175,4 @@ instance FromJSONKey Threshold where $(deriveJSON defaultOptions ''RateAssumption) $(deriveJSON defaultOptions ''Direction) -$(concat <$> traverse (deriveJSON defaultOptions) [''Limit] ) +$(concat <$> traverse (deriveJSON defaultOptions) [''Limit] ) \ No newline at end of file diff --git a/src/Util.hs b/src/Util.hs index a0cc9ab4..c962e8cd 100644 --- a/src/Util.hs +++ b/src/Util.hs @@ -16,7 +16,7 @@ module Util ,lookupInMap,selectInMap ,lookupTuple6 ,lookupTuple7 -- for debug - ,debugOnDate + ,debugOnDate,paySeqM ) where import qualified Data.Time as T @@ -304,6 +304,7 @@ slice from to xs = take (to - from ) (drop from xs) dropLastN :: Int -> [a] -> [a] dropLastN n xs = slice 0 (length xs - n) xs +-- ^ convert annual rate (in 365 days) to period rate by interval days toPeriodRateByInterval :: Rate -> Int -> Rate toPeriodRateByInterval annualRate days = toRational $ 1 - fromRational (1-annualRate) ** (fromIntegral days / 365) -- `debug` ("days>>"++show days++"DIV"++ show ((fromIntegral days) / 365)) @@ -347,6 +348,24 @@ lstToMapByFn fn lst = in M.fromList $ zip ks lst +paySeqM :: Date -> Amount -> (a->Balance) -> (Amount->a->Either String a) -> Either String [a] -> [a] -> Either String ([a],Amount) +paySeqM d amt getDueAmt payFn paidList [] + = do + pList <- paidList + return (reverse pList, amt) +paySeqM d 0 getDueAmt payFn paidList tobePaidList + = do + pList <- paidList + return (reverse pList++tobePaidList, 0) +paySeqM d amt getDueAmt payFn paidList (l:tobePaidList) + = do + let dueAmt = getDueAmt l + let actualPaidOut = min amt dueAmt + let remainAmt = amt - actualPaidOut + paidL <- payFn actualPaidOut l + paidList_ <- paidList + paySeqM d remainAmt getDueAmt payFn (Right $ paidL:paidList_) tobePaidList + paySequentially :: Date -> Amount -> (a->Balance) -> (Amount->a->a) -> [a] -> [a] -> ([a],Amount) paySequentially d amt getDueAmt payFn paidList [] = (reverse paidList, amt) diff --git a/src/Waterfall.hs b/src/Waterfall.hs index 7f8f00b0..1d7c8f9e 100644 --- a/src/Waterfall.hs +++ b/src/Waterfall.hs @@ -54,6 +54,18 @@ data PayOrderBy = ByName type BookLedger = (BookDirection, LedgerName) type BookLedgers = (BookDirection, [LedgerName]) + +-- data ActionTag = Pay +-- | TransferTo +-- | Accrue +-- | WriteOffTo +-- | Receive +-- | Settle +-- | Buy +-- | Sell + + + data Action = -- Accounts Transfer (Maybe Limit) AccountName AccountName (Maybe TxnComment) diff --git a/src/WebUI.hs b/src/WebUI.hs new file mode 100644 index 00000000..6a1c78e9 --- /dev/null +++ b/src/WebUI.hs @@ -0,0 +1,89 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE TypeOperators #-} + + module WebUI where +-- +-- import Web.Hyperbole +-- import Data.Text (Text) +-- +-- import qualified Accounts as A +-- import Types +-- +-- import Web.Hyperbole +-- import Data.Text (Text) +-- import GHC.Generics (Generic) +-- +-- -- Define your ADT +-- data Shape = +-- Circle Double +-- | Rectangle Double Double +-- | Triangle Double Double Double +-- deriving (Show, Generic, HyperView) +-- +-- instance HyperView Shape where +-- data Action Shape = +-- SetShape Text +-- | SetCircleRadius Double +-- | SetRectangleWidth Double +-- | SetRectangleHeight Double +-- | SetTriangleA Double +-- | SetTriangleB Double +-- | SetTriangleC Double +-- deriving (Show, Read, ViewAction) +-- +-- view _ s = do +-- col id $ do +-- -- Dropdown for shape selection +-- select "shape" (viewShapeName s) SetShape [("Circle", "Circle"), ("Rectangle", "Rectangle"), ("Triangle", "Triangle")] +-- case s of +-- Circle r -> do +-- field "radius" r SetCircleRadius +-- Rectangle w h -> do +-- field "width" w SetRectangleWidth +-- field "height" h SetRectangleHeight +-- Triangle a b c -> do +-- field "side a" a SetTriangleA +-- field "side b" b SetTriangleB +-- field "side c" c SetTriangleC +-- +-- update (SetShape "Circle") _ = Circle 0 +-- update (SetShape "Rectangle") _ = Rectangle 0 0 +-- update (SetShape "Triangle") _ = Triangle 0 0 0 +-- update (SetCircleRadius r) _ = Circle r +-- update (SetRectangleWidth w) (Rectangle _ h) = Rectangle w h +-- update (SetRectangleHeight h) (Rectangle w _) = Rectangle w h +-- update (SetTriangleA a) (Triangle _ b c) = Triangle a b c +-- update (SetTriangleB b) (Triangle a _ c) = Triangle a b c +-- update (SetTriangleC c) (Triangle a b _) = Triangle a b c +-- update _ s = s -- Default case, no change +-- +-- -- Helper function to convert Shape to text for the select dropdown +-- viewShapeName :: Shape -> Text +-- viewShapeName (Circle _) = "Circle" +-- viewShapeName (Rectangle _ _) = "Rectangle" +-- viewShapeName (Triangle _ _ _) = "Triangle" +-- +-- -- Define a simple page +-- page :: (Hyperbole :> es) => Eff es (Page '[Shape]) +-- page = do +-- pure $ col id $ do +-- hyper ShapeForm $ view ShapeForm (Circle 0) +-- +-- +-- +-- main :: IO () +-- main = do +-- run 3001 $ do +-- liveApp (basicDocument "Example") (runPage page) +-- -- +-- -- page :: Eff es (Page '[]) +-- -- page = do +-- -- pure $ do +-- -- col (pad 10) $ do +-- -- hyper MonthEnd dpView "A" \ No newline at end of file diff --git a/stack.yaml b/stack.yaml index 5158bd3e..9a2a3cf8 100644 --- a/stack.yaml +++ b/stack.yaml @@ -8,7 +8,11 @@ # A snapshot resolver dictates the compiler version and the set of packages # to be used for project dependencies. For example: # -resolver: lts-22.34 +resolver: lts-23.5 +# urls: +# latest-snapshot: http://www.stackage.org/download/snapshots.json +# lts-build-plans: http://www.stackage.org/download/lts-build-plans.json +# nightly-build-plans: http://www.stackage.org/download/nightly-build-plans.json # resolver: nightly-2021-12-26 # resolver: nightly-2015-09-21 @@ -80,4 +84,4 @@ extra-deps: # compiler-check: newer-minor # allow-newer : true -pvp-bounds: both +pvp-bounds: both \ No newline at end of file diff --git a/swagger.json b/swagger.json index 3b624fd0..6c30be0a 100644 --- a/swagger.json +++ b/swagger.json @@ -3523,6 +3523,32 @@ ], "title": "DefaultStressByTs", "type": "object" + }, + { + "properties": { + "contents": { + "items": { + "items": { + "format": "double", + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "tag": { + "enum": [ + "DefaultByTerm" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "DefaultByTerm", + "type": "object" } ] }, @@ -4099,6 +4125,52 @@ ], "title": "PrepayStressByTs", "type": "object" + }, + { + "properties": { + "contents": { + "format": "double", + "type": "number" + }, + "tag": { + "enum": [ + "PrepaymentPSA" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "PrepaymentPSA", + "type": "object" + }, + { + "properties": { + "contents": { + "items": { + "items": { + "format": "double", + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "tag": { + "enum": [ + "PrepaymentByTerm" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "PrepaymentByTerm", + "type": "object" } ] }, @@ -4341,11 +4413,8 @@ "multipleOf": 1.0e-2, "type": "number" }, - "bndDueIntDates": { - "items": { - "$ref": "#/components/schemas/Day" - }, - "type": "array" + "bndDueIntDate": { + "$ref": "#/components/schemas/Day" }, "bndDueIntOverInts": { "items": { @@ -4642,6 +4711,25 @@ "title": "PAC", "type": "object" }, + { + "properties": { + "contents": { + "$ref": "#/components/schemas/PerCurve_(Fixed_*_E2)" + }, + "tag": { + "enum": [ + "AmtByPeriod" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "AmtByPeriod", + "type": "object" + }, { "properties": { "contents": { @@ -6362,6 +6450,13 @@ ], "type": "string" }, + "DealStatFields": { + "enum": [ + "PoolCollectedPeriod", + "BondPaidPeriod" + ], + "type": "string" + }, "DealStats": { "oneOf": [ { @@ -7491,21 +7586,14 @@ { "properties": { "contents": { - "items": [ - { - "$ref": "#/components/schemas/Day" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2, + "items": { + "type": "string" + }, "type": "array" }, "tag": { "enum": [ - "FeePaidAt" + "FeePaidAmt" ], "type": "string" } @@ -7514,7 +7602,7 @@ "tag", "contents" ], - "title": "FeePaidAt", + "title": "FeePaidAmt", "type": "object" }, { @@ -8346,6 +8434,25 @@ "title": "CustomData", "type": "object" }, + { + "properties": { + "contents": { + "$ref": "#/components/schemas/DealStatFields" + }, + "tag": { + "enum": [ + "DealStatBalance" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "DealStatBalance", + "type": "object" + }, { "properties": { "contents": { @@ -8464,6 +8571,25 @@ "title": "MonthsTillMaturity", "type": "object" }, + { + "properties": { + "contents": { + "$ref": "#/components/schemas/DealStatFields" + }, + "tag": { + "enum": [ + "DealStatInt" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "DealStatInt", + "type": "object" + }, { "properties": { "contents": { @@ -8726,6 +8852,25 @@ "title": "TriggersStatus", "type": "object" }, + { + "properties": { + "contents": { + "$ref": "#/components/schemas/DealStatFields" + }, + "tag": { + "enum": [ + "DealStatBool" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "DealStatBool", + "type": "object" + }, { "properties": { "contents": { @@ -8971,6 +9116,25 @@ "title": "BondWaRate", "type": "object" }, + { + "properties": { + "contents": { + "$ref": "#/components/schemas/DealStatFields" + }, + "tag": { + "enum": [ + "DealStatRate" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "DealStatRate", + "type": "object" + }, { "properties": { "contents": { @@ -9921,6 +10085,34 @@ } ] }, + "Either_[Char]_FirstLossResult": { + "oneOf": [ + { + "properties": { + "Left": { + "type": "string" + } + }, + "required": [ + "Left" + ], + "title": "Left", + "type": "object" + }, + { + "properties": { + "Right": { + "$ref": "#/components/schemas/FirstLossResult" + } + }, + "required": [ + "Right" + ], + "title": "Right", + "type": "object" + } + ] + }, "ExtraStress": { "properties": { "defaultFactors": { @@ -10323,6 +10515,44 @@ "title": "FeeFlow", "type": "object" }, + { + "properties": { + "contents": { + "$ref": "#/components/schemas/PerCurve_(Fixed_*_E2)" + }, + "tag": { + "enum": [ + "FeeFlowByPoolPeriod" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "FeeFlowByPoolPeriod", + "type": "object" + }, + { + "properties": { + "contents": { + "$ref": "#/components/schemas/PerCurve_(Fixed_*_E2)" + }, + "tag": { + "enum": [ + "FeeFlowByBondPeriod" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "FeeFlowByBondPeriod", + "type": "object" + }, { "properties": { "contents": { @@ -10470,28 +10700,32 @@ } ] }, - "FixedAsset": { + "FirstLossReq": { "oneOf": [ { "properties": { "contents": { "items": [ { - "$ref": "#/components/schemas/OriginalInfo" + "$ref": "#/components/schemas/DealType" }, { - "maximum": 9223372036854775807, - "minimum": -9223372036854775808, - "type": "integer" + "$ref": "#/components/schemas/ApplyAssumptionType" + }, + { + "$ref": "#/components/schemas/NonPerfAssumption" + }, + { + "type": "string" } ], - "maxItems": 2, - "minItems": 2, + "maxItems": 4, + "minItems": 4, "type": "array" }, "tag": { "enum": [ - "FixedAsset" + "FirstLossReq" ], "type": "string" } @@ -10500,14 +10734,14 @@ "tag", "contents" ], - "title": "FixedAsset", + "title": "FirstLossReq", "type": "object" }, { "properties": { "tag": { "enum": [ - "Dummy5" + "Dummyyy" ], "type": "string" } @@ -10515,19 +10749,118 @@ "required": [ "tag" ], - "title": "Dummy5", + "title": "Dummyyy", "type": "object" } ] }, - "Index": { - "enum": [ - "LPR5Y", - "LPR1Y", - "LIBOR1M", - "LIBOR3M", - "LIBOR6M", - "LIBOR1Y", + "FirstLossResult": { + "oneOf": [ + { + "properties": { + "contents": { + "items": [ + { + "format": "double", + "type": "number" + }, + { + "$ref": "#/components/schemas/ApplyAssumptionType" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "tag": { + "enum": [ + "FirstLossResult" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "FirstLossResult", + "type": "object" + }, + { + "properties": { + "tag": { + "enum": [ + "Dummyyyy" + ], + "type": "string" + } + }, + "required": [ + "tag" + ], + "title": "Dummyyyy", + "type": "object" + } + ] + }, + "FixedAsset": { + "oneOf": [ + { + "properties": { + "contents": { + "items": [ + { + "$ref": "#/components/schemas/OriginalInfo" + }, + { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "tag": { + "enum": [ + "FixedAsset" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "FixedAsset", + "type": "object" + }, + { + "properties": { + "tag": { + "enum": [ + "Dummy5" + ], + "type": "string" + } + }, + "required": [ + "tag" + ], + "title": "Dummy5", + "type": "object" + } + ] + }, + "Index": { + "enum": [ + "LPR5Y", + "LPR1Y", + "LIBOR1M", + "LIBOR3M", + "LIBOR6M", + "LIBOR1Y", "USTSY1Y", "USTSY2Y", "USTSY3Y", @@ -10755,26 +11088,6 @@ "title": "Fix", "type": "object" }, - { - "properties": { - "contents": { - "multipleOf": 1.0e-6, - "type": "number" - }, - "tag": { - "enum": [ - "InterestByYield" - ], - "type": "string" - } - }, - "required": [ - "tag", - "contents" - ], - "title": "InterestByYield", - "type": "object" - }, { "properties": { "contents": { @@ -12251,34 +12564,224 @@ ] }, "OriginalInfo": { - "properties": { - "maturityDate": { - "$ref": "#/components/schemas/Day" + "oneOf": [ + { + "properties": { + "obligor": { + "$ref": "#/components/schemas/Obligor" + }, + "originBalance": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "originRate": { + "$ref": "#/components/schemas/RateType" + }, + "originTerm": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "period": { + "$ref": "#/components/schemas/Period" + }, + "prepaymentPenalty": { + "$ref": "#/components/schemas/PrepayPenaltyType" + }, + "prinType": { + "$ref": "#/components/schemas/AmortPlan" + }, + "startDate": { + "$ref": "#/components/schemas/Day" + }, + "tag": { + "enum": [ + "MortgageOriginalInfo" + ], + "type": "string" + } + }, + "required": [ + "originBalance", + "originRate", + "originTerm", + "period", + "startDate", + "prinType", + "tag" + ], + "title": "MortgageOriginalInfo", + "type": "object" }, - "originBalance": { - "multipleOf": 1.0e-2, - "type": "number" + { + "properties": { + "obligor": { + "$ref": "#/components/schemas/Obligor" + }, + "originBalance": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "originRate": { + "$ref": "#/components/schemas/RateType" + }, + "originTerm": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "period": { + "$ref": "#/components/schemas/Period" + }, + "prinType": { + "$ref": "#/components/schemas/AmortPlan" + }, + "startDate": { + "$ref": "#/components/schemas/Day" + }, + "tag": { + "enum": [ + "LoanOriginalInfo" + ], + "type": "string" + } + }, + "required": [ + "originBalance", + "originRate", + "originTerm", + "period", + "startDate", + "prinType", + "tag" + ], + "title": "LoanOriginalInfo", + "type": "object" }, - "originDate": { - "$ref": "#/components/schemas/Day" + { + "properties": { + "obligor": { + "$ref": "#/components/schemas/Obligor" + }, + "originRental": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "originTerm": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "paymentDates": { + "$ref": "#/components/schemas/DatePattern" + }, + "startDate": { + "$ref": "#/components/schemas/Day" + }, + "tag": { + "enum": [ + "LeaseInfo" + ], + "type": "string" + } + }, + "required": [ + "startDate", + "originTerm", + "paymentDates", + "originRental", + "tag" + ], + "title": "LeaseInfo", + "type": "object" }, - "originRate": { - "format": "double", - "type": "number" + { + "properties": { + "accRule": { + "$ref": "#/components/schemas/AmortRule" + }, + "capacity": { + "$ref": "#/components/schemas/Capacity" + }, + "originBalance": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "originTerm": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "period": { + "$ref": "#/components/schemas/Period" + }, + "residualBalance": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "startDate": { + "$ref": "#/components/schemas/Day" + }, + "tag": { + "enum": [ + "FixedAssetInfo" + ], + "type": "string" + } + }, + "required": [ + "startDate", + "originBalance", + "residualBalance", + "originTerm", + "period", + "accRule", + "capacity", + "tag" + ], + "title": "FixedAssetInfo", + "type": "object" + }, + { + "properties": { + "dueDate": { + "$ref": "#/components/schemas/Day" + }, + "feeType": { + "$ref": "#/components/schemas/ReceivableFeeType" + }, + "obligor": { + "$ref": "#/components/schemas/Obligor" + }, + "originAdvance": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "originBalance": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "startDate": { + "$ref": "#/components/schemas/Day" + }, + "tag": { + "enum": [ + "ReceivableInfo" + ], + "type": "string" + } + }, + "required": [ + "startDate", + "originBalance", + "originAdvance", + "dueDate", + "tag" + ], + "title": "ReceivableInfo", + "type": "object" } - }, - "required": [ - "originBalance", - "originDate", - "originRate" - ], - "type": "object" - }, - "OverrideType": { - "items": { - "$ref": "#/components/schemas/ActionOnDate" - }, - "type": "array" + ] }, "PayOrderBy": { "enum": [ @@ -12290,6 +12793,70 @@ ], "type": "string" }, + "PerCurve_(Fixed_*_E2)": { + "oneOf": [ + { + "properties": { + "contents": { + "items": { + "$ref": "#/components/schemas/PerPoint_(Fixed_*_E2)" + }, + "type": "array" + }, + "tag": { + "enum": [ + "CurrentVal" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "CurrentVal", + "type": "object" + }, + { + "properties": { + "contents": { + "items": { + "$ref": "#/components/schemas/PerPoint_(Fixed_*_E2)" + }, + "type": "array" + }, + "tag": { + "enum": [ + "WithTrailVal" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "WithTrailVal", + "type": "object" + } + ] + }, + "PerPoint_(Fixed_*_E2)": { + "items": [ + { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + { + "multipleOf": 1.0e-2, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, "Period": { "enum": [ "Daily", @@ -13996,7 +14563,7 @@ "type": "number" }, { - "multipleOf": 1.0e-2, + "multipleOf": 1.0e-6, "type": "number" }, { @@ -14045,7 +14612,7 @@ "type": "number" }, { - "multipleOf": 1.0e-2, + "multipleOf": 1.0e-6, "type": "number" }, { @@ -16702,12 +17269,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -16750,12 +17311,6 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" - }, - "type": "array" - }, "pool": { "$ref": "#/components/schemas/PoolType_AssetUnion" }, @@ -16771,6 +17326,41 @@ }, "type": "object" }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, + "type": "array" + }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -16802,7 +17392,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -16820,12 +17411,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -16868,27 +17453,56 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" + "pool": { + "$ref": "#/components/schemas/PoolType_FixedAsset" + }, + "rateCap": { + "additionalProperties": { + "$ref": "#/components/schemas/RateCap" + }, + "type": "object" + }, + "rateSwap": { + "additionalProperties": { + "$ref": "#/components/schemas/RateSwap" }, + "type": "object" + }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, "type": "array" }, - "pool": { - "$ref": "#/components/schemas/PoolType_FixedAsset" - }, - "rateCap": { - "additionalProperties": { - "$ref": "#/components/schemas/RateCap" - }, - "type": "object" - }, - "rateSwap": { - "additionalProperties": { - "$ref": "#/components/schemas/RateSwap" - }, - "type": "object" - }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -16920,7 +17534,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -16938,12 +17553,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -16986,12 +17595,6 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" - }, - "type": "array" - }, "pool": { "$ref": "#/components/schemas/PoolType_Installment" }, @@ -17007,6 +17610,41 @@ }, "type": "object" }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, + "type": "array" + }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -17038,7 +17676,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -17056,12 +17695,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -17104,12 +17737,6 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" - }, - "type": "array" - }, "pool": { "$ref": "#/components/schemas/PoolType_Lease" }, @@ -17125,6 +17752,41 @@ }, "type": "object" }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, + "type": "array" + }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -17156,7 +17818,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -17174,12 +17837,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -17222,12 +17879,6 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" - }, - "type": "array" - }, "pool": { "$ref": "#/components/schemas/PoolType_Loan" }, @@ -17243,6 +17894,41 @@ }, "type": "object" }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, + "type": "array" + }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -17274,7 +17960,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -17292,12 +17979,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -17340,12 +18021,6 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" - }, - "type": "array" - }, "pool": { "$ref": "#/components/schemas/PoolType_Mortgage" }, @@ -17361,6 +18036,41 @@ }, "type": "object" }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, + "type": "array" + }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -17392,7 +18102,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -17410,12 +18121,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -17458,12 +18163,6 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" - }, - "type": "array" - }, "pool": { "$ref": "#/components/schemas/PoolType_ProjectedCashflow" }, @@ -17479,6 +18178,41 @@ }, "type": "object" }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, + "type": "array" + }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -17510,7 +18244,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -17528,12 +18263,6 @@ }, "type": "object" }, - "call": { - "items": { - "$ref": "#/components/schemas/CallOption" - }, - "type": "array" - }, "collects": { "items": { "$ref": "#/components/schemas/CollectionRule" @@ -17576,12 +18305,6 @@ "name": { "type": "string" }, - "overrides": { - "items": { - "$ref": "#/components/schemas/OverrideType" - }, - "type": "array" - }, "pool": { "$ref": "#/components/schemas/PoolType_Receivable" }, @@ -17597,6 +18320,41 @@ }, "type": "object" }, + "stats": { + "items": [ + { + "additionalProperties": { + "multipleOf": 1.0e-2, + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "format": "double", + "type": "number" + }, + "type": "object" + }, + { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + { + "additionalProperties": { + "maximum": 9223372036854775807, + "minimum": -9223372036854775808, + "type": "integer" + }, + "type": "object" + } + ], + "maxItems": 4, + "minItems": 4, + "type": "array" + }, "status": { "$ref": "#/components/schemas/DealStatus" }, @@ -17628,7 +18386,8 @@ "bonds", "pool", "waterfall", - "collects" + "collects", + "stats" ], "type": "object" }, @@ -20014,7 +20773,7 @@ "name": "BSD 3" }, "title": "Hastructure API", - "version": "0.41.0" + "version": "0.42.4" }, "openapi": "3.0.0", "paths": { @@ -20077,6 +20836,34 @@ } } }, + "/runByFirstLoss": { + "post": { + "requestBody": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/FirstLossReq" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json;charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Either_[Char]_FirstLossResult" + } + } + }, + "description": "" + }, + "400": { + "description": "Invalid `body`" + } + } + } + }, "/runDate": { "post": { "requestBody": { diff --git a/test/MainTest.hs b/test/MainTest.hs index 9e43e2d6..851d8660 100644 --- a/test/MainTest.hs +++ b/test/MainTest.hs @@ -61,7 +61,9 @@ tests = testGroup "Tests" [AT.mortgageTests ,CFT.testPoolAggTest ,BT.pricingTests ,BT.bndConsolTest + ,BT.writeOffTest ,LT.curveTests + ,LT.periodCurveTest ,LT.pvTests ,LT.seqFunTest -- --,LT.queryStmtTests @@ -91,6 +93,7 @@ tests = testGroup "Tests" [AT.mortgageTests ,UtilT.splitTsTest ,UtilT.tableTest ,UtilT.lastOftest + ,UtilT.paySeqTest ,AccT.intTests ,AccT.investTests ,AccT.reserveAccTest diff --git a/test/UT/AnalyticsTest.hs b/test/UT/AnalyticsTest.hs index eea8ba11..504a09fa 100644 --- a/test/UT/AnalyticsTest.hs +++ b/test/UT/AnalyticsTest.hs @@ -14,6 +14,8 @@ import AssetClass.AssetBase import AssetClass.Loan import InterestRate +import Data.Ratio + walTest = let _ps = [(50,L.toDate "20230630"),(50,L.toDate "20231231")] @@ -35,18 +37,36 @@ durationTest = [ testCase "Duration 1" $ assertEqual "10 Months bullet" - 0.74 + (273 % 365) (calcDuration + DC_ACT_365F (L.toDate "20230101") [(L.toDate "20231001",100)] (L.mkRateTs [(L.toDate "20230101",0.01)])) , testCase "Duration 2" $ assertEqual "Multiple cf" - 0.86 + (252921 % 289445) (calcDuration + DC_ACT_365F (L.toDate "20230101") [(L.toDate "20231001",100),(L.toDate "20240101",100)] (L.mkRateTs [(L.toDate "20230101",0.01)])) + , testCase "Duration 3" $ + assertEqual "12 Months bullet" + (364 % 365) + (calcDuration + DC_ACT_365F + (L.toDate "20230101") + [(L.toDate "20231231",104)] + (L.mkRateTs [(L.toDate "20230101",0.05)])) + , testCase "Convexity 1" $ + assertEqual "10 Months bullet" + (4068161010949933 % 2251799813685248) + (calcConvexity + DC_ACT_365F + (L.toDate "20230101") + [(L.toDate "20231231",104)] + (L.mkRateTs [(L.toDate "20230101",0.05)])) ] fvTest = @@ -65,7 +85,7 @@ assetPricingTest = testGroup "Pricing on Asset" [ testCase "Loan Pricing(Inc Int)" $ assertEqual "Loan Pricing" - (Right (AssetPrice 1037.38 0.76 0.7 (-1.0) 0.21)) + (Right (AssetPrice 1037.38 0.76 0.726208 0.0005369 0.21)) (priceAsset (PersonalLoan (LoanOriginalInfo 1200 (Fix DC_30_360_US 0.08) 12 Monthly (L.toDate "20240701") I_P Nothing) 1000 0.08 10 Current) (L.toDate "20241002") (PvRate 0.03) @@ -74,7 +94,7 @@ assetPricingTest = Inc) ,testCase "Loan Pricing(Exc Int)" $ assertEqual "Loan Pricing" - (Right (AssetPrice 1037.17 0.76 0.7 (-1.0) 0.21)) + (Right (AssetPrice 1037.17 0.76 0.72633840 0.00052012 0.21)) (priceAsset (PersonalLoan (LoanOriginalInfo 1200 (Fix DC_30_360_US 0.08) 12 Monthly (L.toDate "20240701") I_P Nothing) 1000 0.08 10 Current) (L.toDate "20241002") (PvRate 0.03) diff --git a/test/UT/AssetTest.hs b/test/UT/AssetTest.hs index 5ecf9bff..a8b06f0b 100644 --- a/test/UT/AssetTest.hs +++ b/test/UT/AssetTest.hs @@ -127,7 +127,7 @@ mortgageTests = testGroup "Mortgage cashflow Tests" assertEqual "Empty for principal" (0.0, asDay, 1) (CF.mflowPrincipal (head trs) - ,CF.mflowDate (head trs) + ,(view CF.tsDate (head trs)) ,length trs) , testCase "Balloon Mortgage test 1" $ diff --git a/test/UT/BondTest.hs b/test/UT/BondTest.hs index 063a0005..209c2196 100644 --- a/test/UT/BondTest.hs +++ b/test/UT/BondTest.hs @@ -1,4 +1,4 @@ -module UT.BondTest(pricingTests,bndConsolTest) +module UT.BondTest(pricingTests,bndConsolTest,writeOffTest) where import Test.Tasty @@ -20,13 +20,12 @@ import Debug.Trace debug = flip trace b1Txn = [ BondTxn (L.toDate "20220501") 1500 10 500 0.08 510 0 0 Nothing S.Empty - ,BondTxn (L.toDate "20220801") 0 10 1500 0.08 1510 0 0 Nothing S.Empty - ] + ,BondTxn (L.toDate "20220801") 0 10 1500 0.08 1510 0 0 Nothing S.Empty ] b1 = B.Bond{B.bndName="A" ,B.bndType=B.Sequential ,B.bndOriginInfo= B.OriginalInfo{ B.originBalance=3000 - ,B.originDate= T.fromGregorian 2022 1 1 + ,B.originDate= T.fromGregorian 2021 1 1 ,B.originRate= 0.08 ,B.maturityDate = Nothing} ,B.bndInterestInfo= B.Fix 0.08 DC_ACT_365F @@ -34,8 +33,9 @@ b1 = B.Bond{B.bndName="A" ,B.bndRate=0.08 ,B.bndDuePrin=0.0 ,B.bndDueInt=0.0 + ,B.bndDueIntOverInt=0.0 ,B.bndDueIntDate=Nothing - ,B.bndLastIntPay = Just (T.fromGregorian 2022 1 1) + ,B.bndLastIntPay = Just (T.fromGregorian 2021 1 1) ,B.bndLastPrinPay = Just (T.fromGregorian 2022 1 1) ,B.bndStmt=Just (S.Statement b1Txn)} @@ -52,6 +52,7 @@ bfloat = B.Bond{B.bndName="A" ,B.bndDuePrin=0.0 ,B.bndDueInt=0.0 ,B.bndDueIntDate=Nothing + ,B.bndDueIntOverInt=0.0 ,B.bndLastIntPay = Just (T.fromGregorian 2022 1 1) ,B.bndLastPrinPay = Just (T.fromGregorian 2022 1 1) ,B.bndStmt=Just $ S.Statement [ BondTxn (L.toDate "20220501") 1500 10 500 0.08 510 0 0 Nothing S.Empty]} @@ -60,67 +61,52 @@ bfloat = B.Bond{B.bndName="A" pricingTests = testGroup "Pricing Tests" [ let - _ts = (L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.05 - ,L.TsPoint (L.toDate "20240101") 0.05]) - _pv_day = (L.toDate "20220201") - _f_day = (L.toDate "20230201") - _pv = B.pv _ts _pv_day _f_day 103 + _ts = L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.05, L.TsPoint (L.toDate "20240101") 0.05] + _pv_day = L.toDate "20220201" + _f_day = L.toDate "20230201" + _pv = B.pv _ts _pv_day _f_day 103 in testCase "PV test" $ assertEqual "simple PV with flat curve" 98.09 - _pv - , + _pv, let - _pv_day = (L.toDate "20220201") - _f_day = (L.toDate "20230201") - _ts1 = (L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.01 - ,L.TsPoint (L.toDate "20230101") 0.03]) + _pv_day = L.toDate "20220201" + _f_day = L.toDate "20230201" + _ts1 = L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.01, L.TsPoint (L.toDate "20230101") 0.03] _pv1 = B.pv _ts1 _pv_day _f_day 103 _diff1 = _pv1 - 100.0 in testCase "PV test with curve change in middle" $ - assertEqual "simple PV with latest rate point" - 100.0 - _pv1 + assertEqual "simple PV with latest rate point" 100.0 _pv1 , let pr = B.priceBond (L.toDate "20210501") - (L.PricingCurve - [L.TsPoint (L.toDate "20210101") 0.01 - ,L.TsPoint (L.toDate "20230101") 0.02]) + (L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.01, L.TsPoint (L.toDate "20230101") 0.02]) b1 in testCase "flat rate discount " $ assertEqual "Test Pricing on case 01" - (B.PriceResult 1978.46 65.948666 1.18 1.17 2.53 0.0 b1Txn) + (B.PriceResult 1978.46 65.948666 1.18 1.1881448 0.4906438 78.9 b1Txn) pr , let b2Txn = [BondTxn (L.toDate "20220301") 3000 10 300 0.08 310 0 0 Nothing S.Empty ,BondTxn (L.toDate "20220501") 2700 10 500 0.08 510 0 0 Nothing S.Empty - ,BondTxn (L.toDate "20220701") 0 10 3200 0.08 3300 0 0 Nothing S.Empty - ] + ,BondTxn (L.toDate "20220701") 0 10 3200 0.08 3300 0 0 Nothing S.Empty] b2 = b1 { B.bndStmt = Just (S.Statement b2Txn)} pr = B.priceBond (L.toDate "20220201") (L.PricingCurve [L.TsPoint (L.toDate "20220101") 0.01 ,L.TsPoint (L.toDate "20220401") 0.03 - ,L.TsPoint (L.toDate "20220601") 0.05 - ]) + ,L.TsPoint (L.toDate "20220601") 0.05]) b2 in testCase " discount curve with two rate points " $ assertEqual "Test Pricing on case 01" - (B.PriceResult 4049.10 134.97 0.44 0.34 0.46 20.38 b2Txn) - pr --TODO need to confirm - , - let - b3 = b1 {B.bndStmt = Nothing,B.bndInterestInfo = B.InterestByYield 0.02} - in - testCase "pay interest to satisfy on yield" $ - assertEqual "" 60 (B.backoutDueIntByYield (L.toDate "20230101") b3) + (B.PriceResult 4049.10 134.97 0.44 0.364564 0.006030 260.38 b2Txn) + pr --TODO need to confirm in UI , let b4 = b1 @@ -145,8 +131,8 @@ pricingTests = testGroup "Pricing Tests" in testCase "Z spread test" $ assertEqual "Z spread test 01" - (0.175999) - (B.calcZspread (100.0,pday) 0 (1.0,(0.01,0.02),0.03) b6 rateCurve) + (Right 0.176754) + (B.calcZspread (100.0,pday) b6 rateCurve) --(B.calcZspread (500.0,pday) (103.0,1/100) Nothing rateCurve) ] @@ -225,9 +211,23 @@ bndConsolTest = testGroup "Bond consoliation & patchtesting" [ assertEqual "" (Just (S.Statement [ BondTxn (L.toDate "20220501") 1000 0 1000 0.08 0 0 0 (Just 0.5) (S.TxnComments [S.Empty, S.Empty])])) bTestConsol + ] - - - - ] +writeOffTest = + let + d1 = L.toDate "20200101" + bnd1 = B.Bond "A" B.Sequential (B.OriginalInfo 100 d1 0.06 Nothing) (B.Fix 0.05 DC_ACT_365F) Nothing 100 0.08 0 0 0 Nothing Nothing Nothing Nothing + writeAmt1 = 70 + writeAmt2 = 120 + in + testGroup "write off on bond" [ + testCase "write off on bond 1" $ + assertEqual "only 1st bond is written off by 70" + (Right (bnd1 {B.bndBalance = 30,B.bndStmt = Just (S.Statement [S.BondTxn d1 30.00 0.00 0.00 0.000000 0.00 0.00 0.00 Nothing (S.WriteOff "A" 70.00)])})) + (B.writeOff d1 writeAmt1 bnd1), + testCase "over write off on bond 1" $ + assertEqual "over write off on bond 1" + (Left "Insufficient balance to write off 120.00\" bond name \"\"A\"") + (B.writeOff d1 writeAmt2 bnd1) + ] diff --git a/test/UT/DealTest.hs b/test/UT/DealTest.hs index 2f136dd9..387a439e 100644 --- a/test/UT/DealTest.hs +++ b/test/UT/DealTest.hs @@ -153,7 +153,6 @@ td2 = D.TestDeal { ,D.collects = [W.Collect Nothing W.CollectedInterest "General" ,W.Collect Nothing W.CollectedPrincipal "General"] ,D.custom = Nothing - ,D.call = Nothing ,D.liqProvider = Just $ Map.fromList $ [("Liq1",CE.LiqFacility "" @@ -180,14 +179,15 @@ td2 = D.TestDeal { ,Trg.trgStatus = False ,Trg.trgCurable = False })] )] - ,D.overrides = Nothing ,D.ledgers = Nothing + ,D.stats = (Map.empty,Map.empty,Map.empty,Map.empty) } baseDeal = D.TestDeal { D.name = "base deal" ,D.status = Amortizing ,D.rateSwap = Nothing + ,D.stats = (Map.empty,Map.empty,Map.empty,Map.empty) ,D.currencySwap = Nothing ,D.dates = PatternInterval $ (Map.fromList [ @@ -268,10 +268,8 @@ baseDeal = D.TestDeal { ])] ,D.collects = [W.Collect Nothing W.CollectedCash "General"] ,D.custom = Nothing - ,D.call = Nothing ,D.liqProvider = Nothing ,D.triggers = Nothing - ,D.overrides = Nothing ,D.ledgers = Nothing ,D.rateCap = Nothing } diff --git a/test/UT/DealTest2.hs b/test/UT/DealTest2.hs index 24c2e2bc..51a0fccf 100644 --- a/test/UT/DealTest2.hs +++ b/test/UT/DealTest2.hs @@ -136,10 +136,8 @@ td = D.TestDeal { ])] ,D.collects = [W.Collect Nothing W.CollectedCash "General"] ,D.custom = Nothing - ,D.call = Nothing ,D.liqProvider = Nothing ,D.triggers = Nothing - ,D.overrides = Nothing ,D.ledgers = Nothing } diff --git a/test/UT/LibTest.hs b/test/UT/LibTest.hs index d0cee22f..39d9ecc8 100644 --- a/test/UT/LibTest.hs +++ b/test/UT/LibTest.hs @@ -3,7 +3,7 @@ module UT.LibTest(curveTests ,datesTests ,prorataTests ,tsOperationTests - ,pvTests,seqFunTest) + ,pvTests,seqFunTest,periodCurveTest) where import Test.Tasty @@ -49,6 +49,41 @@ curveTests = (getValByDate _priceTs Exc (toDate "20210105")) ] +periodCurveTest = + let + _ts = CurrentVal [PerPoint 0 100, PerPoint 1 200, PerPoint 2 300, PerPoint 4 400] + _rs1 = getValFromPerCurve _ts Future Inc <$> [0,1,2,3,4,5] + _rs2 = getValFromPerCurve _ts Future Exc <$> [0,1,2,3,4,5] + _rs3 = getValFromPerCurve _ts Past Inc <$> [0,1,2,3,4,5] + _rs4 = getValFromPerCurve _ts Past Exc <$> [0,1,2,3,4,5] + _ts1 = WithTrailVal [PerPoint 0 100, PerPoint 1 200, PerPoint 2 300] + _r3 = getValFromPerCurve _ts1 Future Inc 4 + _r4 = getValFromPerCurve _ts1 Future Exc 2 + in + testGroup "Period Curve Tests" + [ + testCase "Query period curve by period:Future" $ + assertEqual + "test 5 period:Future:Inc" + [Just 100, Just 200, Just 300, Just 400, Just 400, Nothing] + _rs1 + ,testCase "Query period curve by period:Future" $ + assertEqual + "test 5 period:Future:Exc" + [Just 200, Just 300, Just 400, Just 400, Nothing, Nothing] + _rs2 + ,testCase "Query period curve by period:Past " $ + assertEqual + "test 5 period:Past:Inc" + [Just 100, Just 200, Just 300, Just 300, Just 400, Just 400] + _rs3 + ,testCase "Query period curve by period:Past " $ + assertEqual + "test 5 period:Past:Exc" + [Nothing, Just 100, Just 200, Just 300, Just 300, Just 400] + _rs4 + ] + --queryStmtTests = testGroup "queryStmtTest" -- [ -- let diff --git a/test/UT/UtilTest.hs b/test/UT/UtilTest.hs index e3617a80..716e98ae 100644 --- a/test/UT/UtilTest.hs +++ b/test/UT/UtilTest.hs @@ -1,6 +1,6 @@ module UT.UtilTest(daycountTests1,daycountTests2,daycountTests3,daycountTests4 ,tsTest,ts2Test,ts3Test,dateVectorPatternTest,paddingTest,dateSliceTest - ,capTest,roundingTest,sliceTest,splitTsTest,tableTest,lastOftest)--,daycountTests3,daycountTests4) + ,capTest,roundingTest,sliceTest,splitTsTest,tableTest,lastOftest,paySeqTest)--,daycountTests3,daycountTests4) where import Test.Tasty @@ -8,10 +8,12 @@ import Test.Tasty.HUnit import qualified Data.Time as T import qualified Cashflow as CF +import qualified Liability as L import Util import DateUtil import Lib import Types +import Stmt import Data.Fixed import Data.Ratio ((%)) @@ -598,3 +600,31 @@ lastOftest = (Just [5]) (Util.lastOf b (not . null)) ] + + +paySeqTest = + let + d1 = toDate "20200101" + bnd1 = L.Bond "A" L.Sequential (L.OriginalInfo 100 d1 0.06 Nothing) (L.Fix 0.05 DC_ACT_365F) Nothing 100 0.08 0 0 0 Nothing Nothing Nothing Nothing + bnd2 = L.Bond "B" L.Sequential (L.OriginalInfo 100 d1 0.06 Nothing) (L.Fix 0.05 DC_ACT_365F) Nothing 100 0.08 0 0 0 Nothing Nothing Nothing Nothing + writeAmt1 = 100 + in + testGroup "write off on bond" [ + testCase "write off on bond 1" $ + assertEqual "only 1st bond is written off by 70" + (Right ([bnd1 {L.bndBalance = 30,L.bndStmt = Just (Statement [BondTxn d1 30.00 0.00 0.00 0.000000 0.00 0.00 0.00 Nothing (WriteOff "A" 70.00)])} + , bnd2],0)) + (paySeqM d1 70 L.bndBalance (L.writeOff d1) (Right []) [bnd1,bnd2]) + ,testCase "write off on bond 2" $ + assertEqual "2st bond is written off by 70" + (Right ([bnd1 {L.bndBalance = 0,L.bndStmt = Just (Statement [BondTxn d1 0.00 0.00 0.00 0.000000 0.00 0.00 0.00 Nothing (WriteOff "A" 100.00)])} + , bnd2{L.bndBalance = 70,L.bndStmt = Just (Statement [BondTxn d1 70.00 0.00 0.00 0.000000 0.00 0.00 0.00 Nothing (WriteOff "B" 30.00)])} + ],0)) + (paySeqM d1 130 L.bndBalance (L.writeOff d1) (Right []) [bnd1,bnd2]) + ,testCase "write off on all bonds " $ + assertEqual "over write off" + (Right ([bnd1 {L.bndBalance = 0,L.bndStmt = Just (Statement [BondTxn d1 0.00 0.00 0.00 0.000000 0.00 0.00 0.00 Nothing (WriteOff "A" 100.00)])} + , bnd2{L.bndBalance = 0,L.bndStmt = Just (Statement [BondTxn d1 0.00 0.00 0.00 0.000000 0.00 0.00 0.00 Nothing (WriteOff "B" 100.00)])} + ],30)) + (paySeqM d1 230 L.bndBalance (L.writeOff d1) (Right []) [bnd1,bnd2]) + ] \ No newline at end of file