From 151cf4d90f652fb8057534ef66a47103d21b0237 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Wed, 17 Sep 2025 12:02:38 +0800 Subject: [PATCH 01/17] update --- shell.nix | 1 + src/Deal/DealCollection.hs | 13 ++++++++----- src/Deal/DealRun.hs | 8 +++++--- src/Types.hs | 6 +++--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/shell.nix b/shell.nix index 022b7370..fd40c0e1 100644 --- a/shell.nix +++ b/shell.nix @@ -5,6 +5,7 @@ pkgs.mkShell { cabal2nix haskell.compiler.ghc912 haskell-language-server + python313Packages.towncrier ghciwatch just ]; diff --git a/src/Deal/DealCollection.hs b/src/Deal/DealCollection.hs index 4cd85075..0e7733a9 100644 --- a/src/Deal/DealCollection.hs +++ b/src/Deal/DealCollection.hs @@ -26,9 +26,12 @@ import Util import Lib import Control.Lens hiding (element) -data CollectionRule = Collect (Maybe [PoolId]) PoolSource AccountName -- ^ collect a pool source from pool collection and deposit to an account - | CollectByPct (Maybe [PoolId]) PoolSource [(Rate,AccountName)] -- ^ collect a pool source from pool collection and deposit to multiple accounts with percentages - deriving (Show,Generic,Eq,Ord) +data CollectionRule + -- | collect a pool source from pool collection and deposit to an account + = Collect (Maybe [PoolId]) PoolSource AccountName + -- | collect a pool source from pool collection and deposit to multiple accounts with percentages + | CollectByPct (Maybe [PoolId]) PoolSource [(Rate,AccountName)] + deriving (Show,Generic,Eq,Ord) readProceeds :: PoolSource -> CF.TsRow -> Either ErrorRep Balance @@ -55,7 +58,7 @@ extractTxnsFromFlowFrameMap mPids pflowMap = -- ^ deposit cash to account by collection rule -depositInflow :: Date -> CollectionRule -> Map.Map PoolId CF.PoolCashflow -> Map.Map AccountName A.Account -> Either String (Map.Map AccountName A.Account) +depositInflow :: Date -> CollectionRule -> Map.Map PoolId CF.PoolCashflow -> Map.Map AccountName A.Account -> Either ErrorRep (Map.Map AccountName A.Account) depositInflow d (Collect mPids s an) pFlowMap amap = do amts <- traverse (readProceeds s) txns @@ -81,7 +84,7 @@ depositInflow d (CollectByPct mPids s splitRules) pFlowMap amap --TODO need t -- ^ deposit cash to account by pool map CF and rules -depositPoolFlow :: [CollectionRule] -> Date -> Map.Map PoolId CF.PoolCashflow -> Map.Map String A.Account -> Either String (Map.Map String A.Account) +depositPoolFlow :: [CollectionRule] -> Date -> Map.Map PoolId CF.PoolCashflow -> Map.Map String A.Account -> Either ErrorRep (Map.Map String A.Account) depositPoolFlow rules d pFlowMap amap = foldM (\acc rule -> depositInflow d rule pFlowMap acc) amap rules diff --git a/src/Deal/DealRun.hs b/src/Deal/DealRun.hs index 55bbe0f8..72c22682 100644 --- a/src/Deal/DealRun.hs +++ b/src/Deal/DealRun.hs @@ -311,15 +311,17 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= poolFlowMap collectedFlow = Map.map (bimap fst ((\xs -> [ fst x | x <- xs ]) <$>)) cutOffPoolFlowMap outstandingFlow = Map.map (bimap snd ((\xs -> [ snd x | x <- xs ]) <$>)) cutOffPoolFlowMap - + cutFutureCf = cutBy Exc Future d -- deposit cashflow to SPV from external pool cf in do accs <- depositPoolFlow (collects t) d collectedFlow accMap let dAfterDeposit = (appendCollectedCF d t collectedFlow) {accounts=accs} let newPt = case pool dAfterDeposit of - MultiPool pm -> MultiPool $ (over (mapped . P.poolFutureScheduleCf . _Just . _1 . CF.cashflowTxn) (cutBy Exc Future d)) pm - ResecDeal dMap -> ResecDeal $ (over (mapped . uDealFutureScheduleCf . _Just . CF.cashflowTxn) (cutBy Exc Future d)) dMap + MultiPool pm -> + MultiPool $ (over (mapped . P.poolFutureScheduleCf . _Just . _1 . CF.cashflowTxn) cutFutureCf) pm + ResecDeal dMap -> + ResecDeal $ (over (mapped . uDealFutureScheduleCf . _Just . CF.cashflowTxn) cutFutureCf) dMap let runContext = RunContext outstandingFlow rAssump rates (dRunWithTrigger0, rc1, ads2, newLogs0) <- runTriggers (dAfterDeposit {pool = newPt},runContext,ads) d EndCollection let eopActionsLog = DL.fromList [ RunningWaterfall d W.EndOfPoolCollection | Map.member W.EndOfPoolCollection waterfallM ] diff --git a/src/Types.hs b/src/Types.hs index a876f3c5..aba7f6d1 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -205,8 +205,8 @@ data DayCount = DC_30E_360 -- ^ ISMA European 30S/360 Special German Eurob data DateType = ClosingDate -- ^ deal closing day | CutoffDate -- ^ after which, the pool cashflow was aggregated to SPV | FirstPayDate -- ^ first payment day for bond/waterfall to run with - | NextPayDate - | NextCollectDate + | NextPayDate -- ^ next payment day for bond/waterfall to run + | NextCollectDate -- ^ next collection day for pool | FirstCollectDate -- ^ first collection day for pool | LastCollectDate -- ^ last collection day for pool | LastPayDate -- ^ last payment day for bond/waterfall @@ -249,7 +249,7 @@ data Period = Daily | Quarterly | SemiAnnually | Annually - deriving (Show,Eq,Generic,Ord) + deriving (Show, Eq, Generic, Ord) type DateVector = (Date, DatePattern) From 2cd4130d25e0c54665878e53adfc13710b8157eb Mon Sep 17 00:00:00 2001 From: yellowbean Date: Thu, 18 Sep 2025 00:18:54 +0800 Subject: [PATCH 02/17] UT for refactor on ledger booking --- src/Accounts.hs | 6 +- src/Asset.hs | 14 +++-- src/Call.hs | 33 ++++++---- src/Cashflow.hs | 2 - src/Deal/DealAction.hs | 18 +++--- src/Deal/DealQuery.hs | 17 +++-- src/Deal/DealRun.hs | 6 +- src/Ledger.hs | 140 +++++++++++++++++++---------------------- src/Pool.hs | 13 ++-- src/Stmt.hs | 15 +++-- src/Types.hs | 2 +- test/MainTest.hs | 2 + test/UT/LedgerTest.hs | 77 +++++++++++++++++++++++ 13 files changed, 217 insertions(+), 128 deletions(-) create mode 100644 test/UT/LedgerTest.hs diff --git a/src/Accounts.hs b/src/Accounts.hs index 0272253c..164a8fb8 100644 --- a/src/Accounts.hs +++ b/src/Accounts.hs @@ -52,14 +52,14 @@ data ReserveAmount data Account = Account { accBalance :: Balance -- ^ account current balance - ,accName :: String -- ^ account name + ,accName :: AccountName -- ^ account name ,accInterest :: Maybe InterestInfo -- ^ account reinvestment interest ,accType :: Maybe ReserveAmount -- ^ target info if a reserve account ,accStmt :: Maybe Statement -- ^ transactional history } deriving (Show, Generic, Eq, Ord) -- | build interest earn actions -buildEarnIntAction :: [Account] -> Date -> [(String,Dates)] -> [(String,Dates)] +buildEarnIntAction :: [Account] -> Date -> [(AccountName,Dates)] -> [(AccountName,Dates)] buildEarnIntAction [] ed r = r buildEarnIntAction (acc:accs) ed r = case accInterest acc of @@ -69,6 +69,8 @@ buildEarnIntAction (acc:accs) ed r = Just (InvestmentAccount _ _ dp _ lastAccDate _) -> buildEarnIntAction accs ed [(accName acc, genSerialDatesTill2 NO_IE lastAccDate dp ed)]++r + +-- | accrue interest from last reset date to today accrueInt :: Date -> Account -> Balance accrueInt _ (Account _ _ Nothing _ _) = 0 -- ^ bank account type interest diff --git a/src/Asset.hs b/src/Asset.hs index b0d16c8f..ee3783c8 100644 --- a/src/Asset.hs +++ b/src/Asset.hs @@ -157,11 +157,15 @@ applyExtraStress Nothing _ ppy def = (ppy,def) applyExtraStress (Just ExtraStress{A.defaultFactors= mDefFactor ,A.prepaymentFactors = mPrepayFactor}) ds ppy def = case (mPrepayFactor,mDefFactor) of - (Nothing,Nothing) -> (ppy,def) - (Nothing,Just defFactor) -> (ppy ,getTsVals $ multiplyTs Exc (zipTs ds def) defFactor) - (Just ppyFactor,Nothing) -> (getTsVals $ multiplyTs Exc (zipTs ds ppy) ppyFactor, def) - (Just ppyFactor,Just defFactor) -> (getTsVals $ multiplyTs Exc (zipTs ds ppy) ppyFactor - ,getTsVals $ multiplyTs Exc (zipTs ds def) defFactor) + (Nothing,Nothing) + -> (ppy,def) + (Nothing,Just defFactor) + -> (ppy ,getTsVals $ multiplyTs Exc (zipTs ds def) defFactor) + (Just ppyFactor,Nothing) + -> (getTsVals $ multiplyTs Exc (zipTs ds ppy) ppyFactor, def) + (Just ppyFactor,Just defFactor) + -> (getTsVals $ multiplyTs Exc (zipTs ds ppy) ppyFactor + ,getTsVals $ multiplyTs Exc (zipTs ds def) defFactor) -- ^ convert annual CPR to single month mortality cpr2smm :: Rate -> Rate diff --git a/src/Call.hs b/src/Call.hs index 9f2ba84e..21fbe017 100644 --- a/src/Call.hs +++ b/src/Call.hs @@ -14,16 +14,27 @@ import Data.Aeson.TH import Data.Aeson.Types import GHC.Generics -data CallOption = PoolBalance Balance -- ^ triggered when pool perform balance below threshold - | BondBalance Balance -- ^ triggered when bond current balance below threshold - | PoolFactor Rate -- ^ triggered when pool factor (pool perform balance/origin balance) - | BondFactor Rate -- ^ triggered when bond factor (total bonds current balance / origin balance) - | OnDate Date -- ^ triggered at date - | AfterDate Date -- ^ triggered when after date - | And [CallOption] -- ^ triggered when all options were satisfied - | Or [CallOption] -- ^ triggered when any option is satisfied - | PoolPv Balance -- ^ Call when PV of pool fall below - | Pre Pre -- ^ triggered when predicate evaluates to be True - deriving (Show,Generic,Ord,Eq,Read) +data CallOption + -- | triggered when pool perform balance below threshold + = PoolBalance Balance + -- | triggered when bond current balance below threshold + | BondBalance Balance + -- | triggered when pool factor (pool perform balance/origin balance) + | PoolFactor Rate + -- | triggered when bond factor (total bonds current balance / origin balance) + | BondFactor Rate + -- | triggered at date + | OnDate Date + -- | triggered when after date + | AfterDate Date + -- | triggered when all options were satisfied + | And [CallOption] + -- | triggered when any option is satisfied + | Or [CallOption] + -- | Call when PV of pool fall below + | PoolPv Balance + -- | triggered when predicate evaluates to be True + | Pre Pre + deriving (Show,Generic,Ord,Eq,Read) $(deriveJSON defaultOptions ''CallOption) diff --git a/src/Cashflow.hs b/src/Cashflow.hs index 2c0ad7fc..2980d8e0 100644 --- a/src/Cashflow.hs +++ b/src/Cashflow.hs @@ -110,7 +110,6 @@ type NewDepreciation = Balance type AccuredFee = Balance type FeePaid = Balance -startOfTime = T.fromGregorian 1900 1 1 data TsRow = CashFlow Date Amount | BondFlow Date Balance Principal Interest @@ -120,7 +119,6 @@ data TsRow = CashFlow Date Amount | LeaseFlow Date Balance Rental Default | FixedFlow Date Balance NewDepreciation Depreciation Balance Balance -- unit cash | ReceivableFlow Date Balance AccuredFee Principal FeePaid Default Recovery Loss (Maybe CumulativeStat) - -- | MixedCashflow Date Balance Principal Interest Prepayment deriving(Show,Eq,Ord,Generic,NFData) instance Semigroup TsRow where diff --git a/src/Deal/DealAction.hs b/src/Deal/DealAction.hs index 22a47c78..fe1a0eb9 100644 --- a/src/Deal/DealAction.hs +++ b/src/Deal/DealAction.hs @@ -468,7 +468,9 @@ drawExtraSupport d amt (W.SupportAccount an (Just (dr, ln))) t@TestDeal{accounts let drawAmt = min (A.accBalance acc) amt let oustandingAmt = amt - drawAmt newAccMap <- adjustM (A.draw d drawAmt Types.SupportDraw) an accMap - return (t {accounts = newAccMap ,ledgers = Just $ Map.adjust (LD.entryLog drawAmt d (TxnDirection dr)) ln ledgerMap} , oustandingAmt) + return (t {accounts = newAccMap + ,ledgers = Just $ Map.adjust (LD.entryLogByDr (dr,drawAmt) d Nothing) ln ledgerMap} + , oustandingAmt) -- ^ draw account support drawExtraSupport d amt (W.SupportAccount an Nothing) t@TestDeal{accounts=accMap} @@ -790,7 +792,7 @@ performAction d t@TestDeal{accounts=accMap, ledgers = Just ledgerM} targetAcc <- lookupM an2 accMap (transferAmt,accDrawAmt,_) <- calcAvailAfterLimit t d sourceAcc Nothing (A.accBalance sourceAcc) mLimit (sourceAcc', targetAcc') <- A.transfer (sourceAcc,targetAcc) d transferAmt - let newLedgerM = Map.adjust (LD.entryLog transferAmt d (TxnDirection dr)) lName ledgerM + let newLedgerM = Map.adjust (LD.entryLogByDr (dr, transferAmt) d Nothing) lName ledgerM return t {accounts = Map.insert an1 sourceAcc' (Map.insert an2 targetAcc' accMap) , ledgers = Just newLedgerM} @@ -815,13 +817,13 @@ performAction d t@TestDeal{ledgers= Just ledgerM} (W.BookBy (W.Till ledger dr ds targetAmt <- queryCompound t d ds ledgerI <- lookupM ledger ledgerM let (bookDirection, amtToBook) = LD.bookToTarget ledgerI (dr, fromRational targetAmt) - let newLedgerM = Map.adjust (LD.entryLogByDr bookDirection amtToBook d Nothing) ledger ledgerM + let newLedgerM = Map.adjust (LD.entryLogByDr (bookDirection,amtToBook) d Nothing) ledger ledgerM return $ t {ledgers = Just newLedgerM } performAction d t@TestDeal{ledgers= Just ledgerM} (W.BookBy (W.ByDS ledger dr ds)) = do amtToBook <- queryCompound t d ds - let newLedgerM = Map.adjust (LD.entryLogByDr dr (fromRational amtToBook) d Nothing) ledger ledgerM + let newLedgerM = Map.adjust (LD.entryLogByDr (dr,(fromRational amtToBook)) d Nothing) ledger ledgerM return $ t {ledgers = Just newLedgerM } -- ^ it will book ledgers by order with mandatory caps which describes by a @@ -836,7 +838,7 @@ performAction d t@TestDeal{ledgers= Just ledgerM} (W.BookBy (W.PDL dr ds ledgers ledgCaps <- sequenceA [ queryCompound t d ledgerCap | ledgerCap <- snd <$> ledgersList ] let amtBookedToLedgers = paySeqLiabilitiesAmt (fromRational amtToBook) (fromRational <$> ledgCaps) let newLedgerM = foldr - (\(ln,amt) acc -> Map.adjust (LD.entryLogByDr dr amt d Nothing) ln acc) + (\(ln,amt) acc -> Map.adjust (LD.entryLogByDr (dr,amt) d Nothing) ln acc) ledgerM (zip ledgerNames amtBookedToLedgers) --`debug` ("amts to book"++ show amtBookedToLedgers) return $ t {ledgers = Just newLedgerM} @@ -965,7 +967,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap,ledgers= Just ledgerM} let totalDue = sum dueAmts (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList - let newLedgerM = Map.adjust (LD.entryLogByDr dr paidOutAmt d Nothing) lName ledgerM + let newLedgerM = Map.adjust (LD.entryLogByDr (dr,paidOutAmt) d Nothing) lName ledgerM newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap let dealAfterAcc = t {accounts = newAccMap @@ -1194,7 +1196,7 @@ performAction d t@TestDeal{bonds = bndMap, ledgers = Just ledgerM } bndToWriteOff <- lookupM bnd bndMap let bndBal = L.bndBalance bndToWriteOff writeAmt <- applyLimit t d bndBal bndBal mLimit - let newLedgerM = Map.adjust (LD.entryLogByDr dr writeAmt d (Just (WriteOff bnd writeAmt))) lName ledgerM + let newLedgerM = Map.adjust (LD.entryLogByDr (dr,writeAmt) d (Just (WriteOff bnd writeAmt))) lName ledgerM bndWritedOff <- writeOff d DuePrincipal writeAmt bndToWriteOff return $ t {bonds = Map.fromList [(bnd,bndWritedOff)] <> bndMap, ledgers = Just newLedgerM} @@ -1220,7 +1222,7 @@ performAction d t@TestDeal{bonds=bndMap, ledgers = Just ledgerM} writeAmt <- applyLimit t d totalBondBal totalBondBal mLimit (bndWrited, _) <- paySeqM d writeAmt L.bndBalance (writeOff d DuePrincipal) (Right []) bndsToWriteOff let bndMapUpdated = lstToMapByFn L.bndName bndWrited - let newLedgerM = Map.adjust (LD.entryLogByDr dr writeAmt d Nothing) lName ledgerM + let newLedgerM = Map.adjust (LD.entryLogByDr (dr,writeAmt) d Nothing) lName ledgerM return t {bonds = bndMapUpdated <> bndMap, ledgers = Just newLedgerM} diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index 438f4923..7fc0362b 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -405,7 +405,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f Nothing -> Left ("Date:"++show d++"No ledgers were modeled , failed to find ledger:"++show ans ) Just ledgersM -> do - lgBals <- lookupAndApplies LD.ledgBalance "Ledger Balance" ans ledgersM + lgBals <- lookupAndApplies (snd . LD.ledgBalance) "Ledger Balance" ans ledgersM return $ (toRational . sum) lgBals LedgerBalanceBy dr ans -> @@ -415,9 +415,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f do lgdsM <- selectInMap "Look up ledgers" ans ledgersM let ldgL = Map.elems lgdsM - let bs Credit = filter (\x -> LD.ledgBalance x < 0) ldgL - let bs Debit = filter (\x -> LD.ledgBalance x >= 0) ldgL - return $ toRational $ abs $ sum $ LD.ledgBalance <$> bs dr + return $ toRational $ sum $ (snd . LD.ledgBalance) <$> ldgL FutureCurrentPoolBalance mPns -> case (mPns,pt) of @@ -643,7 +641,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f in case mCmt of Just cmt -> Right . toRational $ sum [ queryTxnAmt lg cmt | lg <- lgs ] - Nothing -> Right . toRational $ sum [ LD.ledgBalance lg | lg <- lgs ] + Nothing -> Right . toRational $ sum [ (snd . LD.ledgBalance) lg | lg <- lgs ] BondBalanceGapAt d bName -> queryCompound t d (Excess [CurrentBondBalanceOf [bName], BondBalanceTarget [bName]]) @@ -817,7 +815,7 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap Just triggerMatCycle -> case Map.lookup tName triggerMatCycle of Nothing -> Left ("Date:"++show d++"no trigger for this deal" ++ show tName ++ " in cycle " ++ show triggerMatCycle) - Just trigger -> Right $ Trg.trgStatus trigger + Just trigger -> return $ Trg.trgStatus trigger Nothing -> Left $ "Date:"++show d++"no trigger for this deal" IsMostSenior bn bns -> @@ -882,7 +880,6 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap 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 TestAll b dss -> allM (\ x -> (== b) <$> queryDealBool t x d ) dss @@ -968,9 +965,9 @@ testPre d t p = q1 <- (queryCompound t d (ps s1)) q2 <- (queryCompound t d (ps s2)) return (toCmp cmp q1 q2) - IfDealStatus st -> Right $ status t == st -- `debug` ("current date"++show d++">> stutus"++show (status t )++"=="++show st) + IfDealStatus st -> return $ status t == st -- `debug` ("current date"++show d++">> stutus"++show (status t )++"=="++show st) - Always b -> Right b + Always b -> return b IfNot _p -> not <$> testPre d t _p where toCmp x = case x of @@ -1027,5 +1024,5 @@ preToStr t d p = where ps = patchDateToStats d -testPre2 :: P.Asset a => Date -> TestDeal a -> Pre -> (String, Either String Bool) +testPre2 :: P.Asset a => Date -> TestDeal a -> Pre -> (String, Either ErrorRep Bool) testPre2 d t p = (preToStr t d p, testPre d t p) diff --git a/src/Deal/DealRun.hs b/src/Deal/DealRun.hs index 72c22682..8927dcb9 100644 --- a/src/Deal/DealRun.hs +++ b/src/Deal/DealRun.hs @@ -315,7 +315,9 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= -- deposit cashflow to SPV from external pool cf in do + -- depsoit collected cashflow to accounts accs <- depositPoolFlow (collects t) d collectedFlow accMap + -- new deal = update accounts and pool collected cashflow let dAfterDeposit = (appendCollectedCF d t collectedFlow) {accounts=accs} let newPt = case pool dAfterDeposit of MultiPool pm -> @@ -594,8 +596,6 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= ostBal = L.getCurBalance bnd prinToPay = min pv ostBal intToPay = max 0 (pv - prinToPay) - -- bnd1 = L.payPrin d prinToPay bnd - -- bnd1 = L.payYield d intToPay bnd in (pay d DuePrincipal prinToPay) =<< (pay d DueResidual intToPay bnd)) bndMap @@ -700,7 +700,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= let runContext = RunContext poolFlowMap rAssump rates newStLogs - | null cleanUpActions = DL.fromList [DealStatusChangeTo d dStatus Called "by Date-Based Call"] + | null cleanUpActions = DL.fromList [DealStatusChangeTo d dStatus Called "by Date-Based Call"] | otherwise = DL.fromList [DealStatusChangeTo d dStatus Called "by Date-Based Call", RunningWaterfall d W.CleanUp] in do diff --git a/src/Ledger.hs b/src/Ledger.hs index ad66ca04..c06e07a3 100644 --- a/src/Ledger.hs +++ b/src/Ledger.hs @@ -2,8 +2,8 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveGeneric #-} -module Ledger (Ledger(..),entryLog,LedgerName,queryGap,clearLedgersBySeq - ,queryDirection,entryLogByDr,bookToTarget) +module Ledger (Ledger(..),LedgerName,queryGap,clearLedgersBySeq + ,entryLogByDr,bookToTarget,bookToClear) where import qualified Data.Time as T import Stmt @@ -25,95 +25,87 @@ debug = flip trace type LedgerName = String +type EntryAmount = (BookDirection, Amount) +type LedgerBalance = (BookDirection, Amount) + +rev :: BookDirection -> BookDirection +rev Credit = Debit +rev Debit = Credit + data Ledger = Ledger { ledgName :: String -- ^ ledger account name - ,ledgBalance :: Balance -- ^ current balance of ledger + ,ledgBalance :: LedgerBalance -- ^ current balance of ledger ,ledgStmt :: Maybe Statement -- ^ ledger transaction history } deriving (Show, Generic,Ord, Eq) --- | Book an entry with date,amount and transaction to a ledger -entryLog :: Amount -> Date -> TxnComment -> Ledger -> Ledger -entryLog amt d cmt ledg@Ledger{ledgStmt = mStmt, ledgBalance = bal} - | isTxnDirection Credit cmt = let - newBal = bal - amt - txn = EntryTxn d newBal amt cmt - in - ledg { ledgStmt = appendStmt txn mStmt,ledgBalance = newBal } - | otherwise = let - newBal = bal + amt - txn = EntryTxn d newBal amt cmt - in - ledg { ledgStmt = appendStmt txn mStmt ,ledgBalance = newBal } - --- TODO-- need to ensure there is no direction in input -entryLogByDr :: BookDirection -> Amount -> Date -> Maybe TxnComment -> Ledger -> Ledger -entryLogByDr dr amt d Nothing = entryLog amt d (TxnDirection dr) -entryLogByDr dr amt d (Just cmt) - | not (hasTxnDirection cmt) = entryLog amt d (TxnComments [TxnDirection dr,cmt]) - | isTxnDirection dr cmt = entryLog amt d cmt - | otherwise = error $ "Suppose direction"++ show dr++"but got from comment"++ show cmt - -entryLogByDr Credit amt d (Just (TxnComments cms)) = entryLog amt d (TxnComments ((TxnDirection Credit):cms)) -entryLogByDr Debit amt d (Just (TxnComments cms)) = entryLog amt d (TxnComments ((TxnDirection Debit):cms)) + +entryLogByDr :: EntryAmount -> Date -> Maybe TxnComment -> Ledger -> Ledger +entryLogByDr (dr, amt) d mCmt ledg@Ledger{ledgStmt = mStmt, ledgBalance = (curDr, curBal)} + = let + cmt = case mCmt of + Nothing -> TxnDirection dr + Just c -> if hasTxnDirection c then c else TxnComments [TxnDirection dr,c] + (newBalAmt, newDr) = case (curDr, dr, amt > curBal ) of + (Debit, Debit, _ ) -> (curBal + amt, Debit) + (Credit, Credit, _) -> (curBal + amt, Credit) + (Debit, Credit, True ) -> (amt - curBal, Credit) + (Debit, Credit, False ) -> (curBal - amt, Debit) + (Credit, Debit, True ) -> (amt - curBal, Debit ) + (Credit, Debit, False ) -> (curBal - amt, Credit) + + txn = EntryTxn d (newDr, newBalAmt) (dr, amt) cmt + in + ledg { ledgStmt = appendStmt txn mStmt ,ledgBalance = (newDr, newBalAmt) } hasTxnDirection :: TxnComment -> Bool hasTxnDirection (TxnDirection _) = True hasTxnDirection (TxnComments txns) = any hasTxnDirection txns hasTxnDirection _ = False -isTxnDirection :: BookDirection -> TxnComment -> Bool -isTxnDirection Credit (TxnDirection Credit) = True -isTxnDirection Debit (TxnDirection Debit) = True -isTxnDirection Credit (TxnComments txns) = any (isTxnDirection Credit) txns -isTxnDirection Debit (TxnComments txns) = any (isTxnDirection Debit) txns -isTxnDirection _ _ = False - --- ^ credit is negative amount -queryDirection :: Ledger -> (BookDirection ,Balance) -queryDirection (Ledger _ bal _) - | bal >= 0 = (Debit, bal) - | bal < 0 = (Credit, negate bal) - -bookToTarget :: Ledger -> (BookDirection,Amount) -> (BookDirection,Amount) -bookToTarget Ledger{ledgBalance = bal} (dr, targetBal) - = case (bal > 0, dr) of - (True, Debit) -> - if (targetBal > bal) then - (Debit,targetBal - bal) - else - (Credit,bal - targetBal) - (False, Credit) -> - if (targetBal > abs bal) then - (Credit,targetBal - abs bal) - else - (Debit, abs bal - targetBal) - (True, Credit) -> - (Credit,targetBal + bal) - (False, Debit) -> - (Debit,targetBal + abs bal) +-- ^ backout book txn from a target amount +bookToTarget :: Ledger -> (BookDirection, Amount) -> (BookDirection, Amount) +bookToTarget Ledger{ledgBalance = (curDr,curBal) } (targetDr, targetBal) + = let + a = 1 + in + case (curDr == targetDr , targetBal >= curBal) of + (True, True) -> + (curDr, targetBal - curBal) + (True, False) -> + (rev curDr, curBal - targetBal) + (False, _) -> + (targetDr, targetBal + curBal) + +bookToClear :: EntryAmount -> Date -> Ledger -> (EntryAmount, Ledger) +bookToClear (_,0) d ledg = ((Credit,0),ledg ) +bookToClear (dr,amt) d ledg@Ledger{ledgBalance = (curDr, curBal)} + | curDr == dr = ((dr, amt), ledg) + | otherwise + = let + bookAmt + | amt > curBal = curBal + | otherwise = amt + remainAmt = amt - bookAmt + newLedger = entryLogByDr (dr, bookAmt) d (Just (TxnDirection dr)) ledg + in + ((dr,remainAmt), newLedger) -- ^ return ledger's bookable amount (for netting off to zero ) with direction input -queryGap :: BookDirection -> Ledger -> Balance -queryGap dr Ledger{ledgBalance = bal} - = case (bal > 0, dr) of - (True, Debit) -> 0 - (True, Credit) -> bal - (False, Debit) -> negate bal - (False, Credit) -> 0 - -clearLedgersBySeq :: BookDirection -> Date -> Amount -> [Ledger] -> [Ledger] -> ([Ledger],Amount) -clearLedgersBySeq dr d 0 rs unAllocLedgers = (rs++unAllocLedgers,0) -clearLedgersBySeq dr d amtToAlloc rs [] = (rs,amtToAlloc) -clearLedgersBySeq dr d amtToAlloc rs (ledger@Ledger{ledgBalance = bal}:ledgers) +queryGap :: Ledger -> LedgerBalance +queryGap Ledger{ledgBalance = (Credit, bal)} = (Debit, bal) -- credit balance can be booked by debit +queryGap Ledger{ledgBalance = (Debit, bal)} = (Credit, bal) -- debit balance can be booked by credit + +-- ^ book an amount to a list of ledgers by sequence +clearLedgersBySeq :: EntryAmount -> Date -> [Ledger] -> [Ledger] -> ([Ledger],EntryAmount) +clearLedgersBySeq (dr,0) d rs unAllocLedgers = ( (reverse rs)++unAllocLedgers,(dr,0)) +clearLedgersBySeq (dr,amtToAlloc) d rs [] = (reverse rs,(dr,amtToAlloc)) +clearLedgersBySeq (dr,amtToAlloc) d rs (ledger:ledgers) = let - deductAmt = queryGap dr ledger - allocAmt = min deductAmt amtToAlloc - remainAmt = amtToAlloc - allocAmt - newLedger = entryLog allocAmt d (TxnDirection dr) ledger + ((newDr, remainAmt), newLedger) = bookToClear (dr,amtToAlloc) d ledger in - clearLedgersBySeq dr d remainAmt (newLedger:rs) ledgers + clearLedgersBySeq (newDr,remainAmt) d (newLedger:rs) ledgers instance QueryByComment Ledger where queryStmt (Ledger _ _ Nothing) tc = [] diff --git a/src/Pool.hs b/src/Pool.hs index e338c238..68143e39 100644 --- a/src/Pool.hs +++ b/src/Pool.hs @@ -195,9 +195,9 @@ pricingPoolFlow d pool@Pool{ futureCf = Just (mCollectedCf,_), issuanceStat = mS in AN.pv21 discountRate d futureDates futureCfCash - -- | run a pool of assets ,use asOfDate of Pool to cutoff cashflow yields from assets with assumptions supplied +-- | run a pool of assets ,use asOfDate of Pool to cutoff cashflow yields from assets with assumptions supplied runPool :: Asset a => Pool a -> Maybe A.ApplyAssumptionType -> Maybe [RateAssumption] - -> Either String [(CF.CashFlowFrame, Map.Map CutoffFields Balance)] + -> Either ErrorRep [(CF.CashFlowFrame, Map.Map CutoffFields Balance)] -- use interest rate assumption runPool (Pool as _ _ asof _ _) Nothing mRates = do @@ -259,15 +259,16 @@ runPool (Pool as _ Nothing asof _ _) (Just (A.ByObligor obligorRules)) mRates = let matchRuleFn (A.FieldIn fv fvals) Nothing = False matchRuleFn (A.FieldIn fv fvals) (Just fm) = case Map.lookup fv fm of - Just (Left v) -> v `elem` fvals - Nothing -> False + Just (Left v) -> v `elem` fvals + Nothing -> False matchRuleFn (A.FieldCmp fv cmp dv) (Just fm) = case Map.lookup fv fm of - Just (Right v) -> case cmp of + Just (Right v) + -> case cmp of G -> v > dv L -> v < dv GE -> v >= dv LE -> v <= dv - Nothing -> False + Nothing -> False matchRuleFn (A.FieldInRange fv rt dv1 dv2) (Just fm) = case Map.lookup fv fm of Just (Right v) -> case rt of diff --git a/src/Stmt.hs b/src/Stmt.hs index 275b1b8b..2de6afc8 100644 --- a/src/Stmt.hs +++ b/src/Stmt.hs @@ -62,7 +62,7 @@ scaleTxn r (ExpTxn d b a b0 t) = ExpTxn d (mulBR b r) (mulBR a r) (mulBR b0 r) t scaleTxn r (SupportTxn d Unlimit b0 i p c t) = SupportTxn d Unlimit (mulBR b0 r) (mulBR i r) (mulBR p r) (mulBR c r) t scaleTxn r (SupportTxn d (ByAvailAmount b) b0 i p c t) = SupportTxn d (ByAvailAmount (mulBR b r)) (mulBR b0 r) (mulBR i r) (mulBR p r) (mulBR c r) t scaleTxn r (IrsTxn d b a i0 i1 b0 t) = IrsTxn d (mulBR b r) (mulBR a r) i0 i1 (mulBR b0 r) t -scaleTxn r (EntryTxn d b a t) = EntryTxn d (mulBR b r) (mulBR a r) t +scaleTxn r (EntryTxn d (cd,b) (cd',a) t) = EntryTxn d (cd,(mulBR b r)) (cd',(mulBR a r)) t scaleByFactor :: Rate -> [Txn] -> [Txn] scaleByFactor r [] = [] @@ -85,7 +85,7 @@ getTxnBalance (BondTxn _ t _ _ _ _ _ _ _ _) = t getTxnBalance (AccTxn _ t _ _ ) = t getTxnBalance (ExpTxn _ t _ _ _ ) = t getTxnBalance (SupportTxn _ _ t _ _ _ _ ) = t -- drawed balance -getTxnBalance (EntryTxn _ t _ _) = t +getTxnBalance (EntryTxn _ (_,t) _ _) = t -- | SupportTxn Date (Maybe Balance) Balance DueInt DuePremium Cash TxnComment @@ -93,7 +93,10 @@ getTxnBegBalance :: Txn -> Balance getTxnBegBalance (BondTxn _ t _ p _ _ _ _ _ _) = t + p getTxnBegBalance (AccTxn _ b a _ ) = b - a getTxnBegBalance (SupportTxn _ _ a b _ _ _) = b + a -getTxnBegBalance (EntryTxn _ a b _) = a + b +getTxnBegBalance (EntryTxn _ (curDr,a) (bkDr,b) _) + | curDr == bkDr && b > a = b - a + | curDr == bkDr && b <= a = a - b + | curDr /= bkDr = a + b getTxnPrincipal :: Txn -> Balance getTxnPrincipal (BondTxn _ _ _ t _ _ _ _ _ _) = t @@ -104,7 +107,7 @@ getTxnAmt (AccTxn _ _ t _ ) = t getTxnAmt (ExpTxn _ _ t _ _ ) = t getTxnAmt (SupportTxn _ _ _ _ _ t _) = t getTxnAmt (IrsTxn _ _ t _ _ _ _ ) = t -getTxnAmt (EntryTxn _ _ t _) = t +getTxnAmt (EntryTxn _ _ t _) = snd t getTxnAmt TrgTxn {} = 0.0 getTxnAsOf :: [Txn] -> Date -> Maybe Txn @@ -116,7 +119,7 @@ emptyTxn AccTxn {} d = AccTxn d 0 0 Empty emptyTxn ExpTxn {} d = ExpTxn d 0 0 0 Empty emptyTxn SupportTxn {} d = SupportTxn d Unlimit 0 0 0 0 Empty emptyTxn IrsTxn {} d = IrsTxn d 0 0 0 0 0 Empty -emptyTxn EntryTxn {} d = EntryTxn d 0 0 Empty +emptyTxn EntryTxn {} d = EntryTxn d (Credit,0) (Credit,0) Empty emptyTxn TrgTxn {} d = TrgTxn d False Empty isEmptyTxn :: Txn -> Bool @@ -125,7 +128,7 @@ isEmptyTxn (AccTxn _ 0 0 Empty) = True isEmptyTxn (ExpTxn _ 0 0 0 Empty) = True isEmptyTxn (SupportTxn _ _ 0 0 0 0 Empty) = True isEmptyTxn (IrsTxn _ 0 0 0 0 0 Empty) = True -isEmptyTxn (EntryTxn _ 0 0 Empty) = True +isEmptyTxn (EntryTxn _ (_,0) (_,0) Empty) = True isEmptyTxn _ = False viewBalanceAsOf :: Date -> [Txn] -> Balance diff --git a/src/Types.hs b/src/Types.hs index aba7f6d1..f7cb4d75 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -618,7 +618,7 @@ data Txn = BondTxn Date Balance Interest Principal IRate Cash DueInt DueIoI (May | ExpTxn Date FeeDue Amount FeeArrears TxnComment -- ^ expense transaction record | SupportTxn Date SupportAvailType Balance DueInt DuePremium Cash TxnComment -- ^ liquidity provider transaction record | IrsTxn Date Balance Amount IRate IRate Balance TxnComment -- ^ interest swap transaction record - | EntryTxn Date Balance Amount TxnComment -- ^ ledger book entry + | EntryTxn Date (BookDirection,Balance) (BookDirection,Amount) TxnComment -- ^ ledger book entry | TrgTxn Date Bool TxnComment deriving (Show, Generic, Eq, Read) diff --git a/test/MainTest.hs b/test/MainTest.hs index b657d07c..21729d67 100644 --- a/test/MainTest.hs +++ b/test/MainTest.hs @@ -19,6 +19,7 @@ import qualified UT.AnalyticsTest as AnalyticsT import qualified UT.InterestRateTest as IRT import qualified UT.RateHedgeTest as RHT import qualified UT.CeTest as CET +import qualified UT.LedgerTest as LeT import qualified DealTest.DealTest as DealTest @@ -117,4 +118,5 @@ tests = testGroup "Tests" [AT.mortgageTests ,DealMultiTest.mPoolbaseTests ,RHT.capRateTests ,CET.liqTest + ,LeT.bookTest ] diff --git a/test/UT/LedgerTest.hs b/test/UT/LedgerTest.hs new file mode 100644 index 00000000..7b4257c1 --- /dev/null +++ b/test/UT/LedgerTest.hs @@ -0,0 +1,77 @@ +module UT.LedgerTest(bookTest) +where + +import Test.Tasty +import Test.Tasty.HUnit +import Accounts +import Lib +import Stmt +import Util +import DateUtil +import Types +import Deal +import Deal.DealQuery (queryCompound) +import Deal.DealBase +import Control.Lens hiding (element,Empty) +import Control.Lens.TH +import Data.Map.Lens +import qualified Ledger as LD + + +import qualified Data.Time as T +import qualified Data.DList as DL +import qualified Data.Map as Map + +bookTest = + let + leg1 = LD.Ledger "L1" (Debit, 200) Nothing + leg2 = LD.Ledger "L2" (Debit, 100) Nothing + + btoClear = LD.bookToClear (Credit, 50) (toDate "20220101") leg1 + btoClear' = LD.bookToClear (Credit, 250) (toDate "20220101") leg1 + bySeq = LD.clearLedgersBySeq (Credit, 240) (toDate "20220101") [] [leg1,leg2] + bySeq' = LD.clearLedgersBySeq (Credit, 350) (toDate "20220101") [] [leg1,leg2] + in + testGroup "Booking Ledger Test" + [ + testCase "Booking Ledger Test:01" $ + assertEqual "01" + (Debit, 250) + (LD.ledgBalance (LD.entryLogByDr (Debit,50) (toDate "20220101") Nothing leg1)) + ,testCase "Booking Ledger Test:02" $ + assertEqual "02" + (Credit, 50) + (LD.ledgBalance (LD.entryLogByDr (Credit,250) (toDate "20220101") Nothing leg1)) + ,testCase "Booking Ledger Test:03" $ + assertEqual "03" + (Debit, 150) + (LD.ledgBalance (LD.entryLogByDr (Credit,50) (toDate "20220101") Nothing leg1)) + ,testCase "Booking Ledger Test:04" $ + assertEqual "04" + (Credit, 250) + (LD.bookToTarget leg1 (Credit,50)) + ,testCase "Booking Ledger Test:05" $ + assertEqual "05" + (Debit, 150) + (LD.bookToTarget leg1 (Debit,350)) + ,testCase "Booking Ledger Test:06" $ + assertEqual "06" + (Credit, 50) + (LD.bookToTarget leg1 (Debit,150)) + ,testCase "Booking Ledger Test:07" $ + assertEqual "07" + ((Debit, 150), 0) + ((LD.ledgBalance . snd) btoClear, (snd . fst) btoClear) + ,testCase "Booking Ledger Test:08" $ + assertEqual "08" + ((Debit, 0), (Credit, 50)) + ((LD.ledgBalance . snd) btoClear', fst btoClear') + ,testCase "Booking Ledger Test:09" $ + assertEqual "09" + [(Debit, 60),(Debit, 0), (Credit, 0)] + [LD.ledgBalance ((fst bySeq) !! 1), LD.ledgBalance ((fst bySeq) !! 0), snd bySeq] + ,testCase "Booking Ledger Test:10" $ + assertEqual "10" + [(Debit, 0),(Debit, 0), (Credit, 50)] + [LD.ledgBalance ((fst bySeq') !! 1), LD.ledgBalance ((fst bySeq') !! 0), snd bySeq'] + ] From 8ec7a6516e74a0e1fc518743217e026cdbe73220 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Sun, 22 Feb 2026 13:47:44 +0800 Subject: [PATCH 03/17] bump version to-> < 0.52.1 > --- Hastructure.cabal | 8 ++++---- app/Main.hs | 2 +- swagger.json | 42 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/Hastructure.cabal b/Hastructure.cabal index de03e6d9..8dda983e 100644 --- a/Hastructure.cabal +++ b/Hastructure.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: Hastructure -version: 0.52.0 +version: 0.52.1 synopsis: Cashflow modeling library for structured finance description: Please see the README on GitHub at category: StructuredFinance,Securitisation,Cashflow @@ -13,7 +13,7 @@ homepage: https://github.com/absbox/Hastructure#readme bug-reports: https://github.com/absbox/Hastructure/issues author: Xiaoyu maintainer: always.zhang@gmail.com -copyright: 2025 Xiaoyu, Zhang +copyright: 2026 Xiaoyu, Zhang license: BSD-3-Clause license-file: LICENSE build-type: Simple @@ -90,13 +90,13 @@ library regex-base >= 0.94.0 && < 0.95, aeson >= 2.2.3 && < 2.3, aeson-gadt-th >= 0.2.5.4 && < 0.3, - hashable >= 1.4.7 && < 1.5.1, + hashable >= 1.4 && <= 1.5.1.0, dlist >= 1.0 && < 1.1, scientific >= 0.3.8 && < 0.4, vector >= 0.13.2 && < 0.14, aeson-pretty >= 0.8.10 && < 0.9, base-compat >= 0.13.0 && < 0.15, - lens >= 5.2.3 && < 5.3.6, + lens >= 5.2.3 && < 5.4, parallel >= 3.2.2 && < 3.3, math-functions >= 0.3.4 && < 0.4, monad-loops >= 0.4.3 && < 0.5, diff --git a/app/Main.hs b/app/Main.hs index f79f004d..a909c90c 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -100,7 +100,7 @@ debug = flip Debug.Trace.trace version1 :: Version -version1 = Version "0.52.0" +version1 = Version "0.52.1" wrapRun :: [D.ExpectReturn] -> DealType -> Maybe AP.ApplyAssumptionType -> AP.NonPerfAssumption -> RunResp diff --git a/swagger.json b/swagger.json index 4375dcbd..0e03c2fa 100644 --- a/swagger.json +++ b/swagger.json @@ -11031,8 +11031,18 @@ "Ledger": { "properties": { "ledgBalance": { - "multipleOf": 1.0e-2, - "type": "number" + "items": [ + { + "$ref": "#/components/schemas/BookDirection" + }, + { + "multipleOf": 1.0e-2, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, "ledgName": { "type": "string" @@ -20301,12 +20311,32 @@ "$ref": "#/components/schemas/Day" }, { - "multipleOf": 1.0e-2, - "type": "number" + "items": [ + { + "$ref": "#/components/schemas/BookDirection" + }, + { + "multipleOf": 1.0e-2, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, { - "multipleOf": 1.0e-2, - "type": "number" + "items": [ + { + "$ref": "#/components/schemas/BookDirection" + }, + { + "multipleOf": 1.0e-2, + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" }, { "$ref": "#/components/schemas/TxnComment" From 4a89da06c5ff2839b915a78f17c7b6806e792de5 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Sun, 22 Feb 2026 17:51:21 +0800 Subject: [PATCH 04/17] bump version to-> < 0.52.2 > --- Hastructure.cabal | 2 +- app/Main.hs | 2 +- swagger.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Hastructure.cabal b/Hastructure.cabal index 8dda983e..39c17966 100644 --- a/Hastructure.cabal +++ b/Hastructure.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: Hastructure -version: 0.52.1 +version: 0.52.2 synopsis: Cashflow modeling library for structured finance description: Please see the README on GitHub at category: StructuredFinance,Securitisation,Cashflow diff --git a/app/Main.hs b/app/Main.hs index a909c90c..0e42adc7 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -100,7 +100,7 @@ debug = flip Debug.Trace.trace version1 :: Version -version1 = Version "0.52.1" +version1 = Version "0.52.2" wrapRun :: [D.ExpectReturn] -> DealType -> Maybe AP.ApplyAssumptionType -> AP.NonPerfAssumption -> RunResp diff --git a/swagger.json b/swagger.json index 0e03c2fa..b3a88587 100644 --- a/swagger.json +++ b/swagger.json @@ -21312,7 +21312,7 @@ "name": "BSD 3" }, "title": "Hastructure API", - "version": "0.52.0" + "version": "0.52.1" }, "openapi": "3.0.0", "paths": { From 306b718785b6d2049f136f6df3e5b21435aa8969 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Fri, 27 Feb 2026 07:58:18 +0800 Subject: [PATCH 05/17] relax the IRR amount --- src/Analytics.hs | 4 ++-- src/Deal/DealAction.hs | 11 ++++++++++- src/Deal/DealQuery.hs | 13 +------------ 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Analytics.hs b/src/Analytics.hs index ea7c51ef..5f1bfb5f 100644 --- a/src/Analytics.hs +++ b/src/Analytics.hs @@ -255,9 +255,9 @@ calcRequiredAmtForIrrAtDate irr ds vs d = itertimes = 500 def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.00000001} in - case ridders def (0.0001,100000000000000) (calcPvFromIRR irr ds vs d) of + case ridders def (-100000000000.0,100000000000000) (calcPvFromIRR irr ds vs d) of Root finalAmt -> Just (fromRational (toRational finalAmt)) - _ -> Nothing + error -> Nothing `debug` ("calcRequiredAmtForIrrAtDate: error"++ show error) -- ^ calc IRR from a cashflow calcIRR :: [Date] -> [Amount] -> Either String Rate diff --git a/src/Deal/DealAction.hs b/src/Deal/DealAction.hs index fe1a0eb9..5fe816e9 100644 --- a/src/Deal/DealAction.hs +++ b/src/Deal/DealAction.hs @@ -460,7 +460,7 @@ evalExtraSupportBalance d t (W.MultiSupport supports) -- ^ draw support from a deal , return updated deal,and remaining oustanding amount -drawExtraSupport :: Date -> Amount -> W.ExtraSupport -> TestDeal a -> Either ErrorRep (TestDeal a, Amount) +drawExtraSupport :: Ast.Asset a => Date -> Amount -> W.ExtraSupport -> TestDeal a -> Either ErrorRep (TestDeal a, Amount) -- ^ draw account support and book ledger drawExtraSupport d amt (W.SupportAccount an (Just (dr, ln))) t@TestDeal{accounts=accMap, ledgers= Just ledgerMap} = do @@ -499,6 +499,15 @@ drawExtraSupport d amt (W.MultiSupport supports) t (t, amt) supports +drawExtraSupport d amt (W.WithCondition pre s) t + = do + flag <- testPre d t pre + if flag then + drawExtraSupport d amt s t + else + return (t, amt) + + inspectListVars :: Ast.Asset a => TestDeal a -> Date -> [DealStats] -> Either ErrorRep [ResultComponent] inspectListVars t d dss = sequenceA [ inspectVars t d ds | ds <- dss] diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index 7fc0362b..6b4df676 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -432,18 +432,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f Left $ "Date:"++show d++"Failed to find pool balance" ++ show pids ++ " from deal "++ show (Map.keys pm) _ -> Left $ "Date:"++show d++"Failed to find pool" ++ show mPns ++","++ show pt --- FutureCurrentSchedulePoolBalance mPns -> --- let --- scheduleFlowM = Map.elems $ view dealScheduledCashflow t --- in --- Right . toRational $ sum $ ((view CF.tsRowBalance) . head . view CF.cashflowTxn) <$> scheduleFlowM --- --- FutureCurrentSchedulePoolBegBalance mPns -> --- let --- scheduleFlowM = Map.elems $ view dealScheduledCashflow t --- in --- Right . toRational $ sum $ (CF.mflowBegBalance . head . view CF.cashflowTxn) <$> scheduleFlowM - + FutureCurrentPoolBegBalance mPns -> let ltc = getLatestCollectFrame t mPns From 88d4e1880bc52007dc1e17a2978bf7e8edf27aa9 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Fri, 27 Feb 2026 08:05:08 +0800 Subject: [PATCH 06/17] bump version to-> < 0.52.3 > --- Hastructure.cabal | 2 +- app/Main.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Hastructure.cabal b/Hastructure.cabal index 39c17966..67a47688 100644 --- a/Hastructure.cabal +++ b/Hastructure.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: Hastructure -version: 0.52.2 +version: 0.52.3 synopsis: Cashflow modeling library for structured finance description: Please see the README on GitHub at category: StructuredFinance,Securitisation,Cashflow diff --git a/app/Main.hs b/app/Main.hs index 0e42adc7..d2c37144 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -100,7 +100,7 @@ debug = flip Debug.Trace.trace version1 :: Version -version1 = Version "0.52.2" +version1 = Version "0.52.3" wrapRun :: [D.ExpectReturn] -> DealType -> Maybe AP.ApplyAssumptionType -> AP.NonPerfAssumption -> RunResp From de48d50b88e5567aad8090393712f828136fe3b6 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Sat, 28 Feb 2026 02:03:48 +0800 Subject: [PATCH 07/17] expose formula: irrOftheBond --- src/Analytics.hs | 4 ++-- src/Deal/DealQuery.hs | 8 ++++++++ src/Types.hs | 1 + swagger.json | 21 ++++++++++++++++++++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Analytics.hs b/src/Analytics.hs index 5f1bfb5f..c64d5580 100644 --- a/src/Analytics.hs +++ b/src/Analytics.hs @@ -257,7 +257,7 @@ calcRequiredAmtForIrrAtDate irr ds vs d = in case ridders def (-100000000000.0,100000000000000) (calcPvFromIRR irr ds vs d) of Root finalAmt -> Just (fromRational (toRational finalAmt)) - error -> Nothing `debug` ("calcRequiredAmtForIrrAtDate: error"++ show error) + error -> Nothing -- `debug` ("calcRequiredAmtForIrrAtDate: error"++ show error) -- ^ calc IRR from a cashflow calcIRR :: [Date] -> [Amount] -> Either String Rate @@ -276,6 +276,6 @@ calcIRR ds vs sumOfPv irr = pv22 irr beginDate ds vs' in case ridders def (-1,1000) sumOfPv of - Root irrRate -> Right $ toRational irrRate + Root irrRate -> return $ toRational irrRate NotBracketed -> Left $ "IRR: not bracketed" ++ show vs' ++ " and dates"++ show ds SearchFailed -> Left $ "IRR: search failed: can't be calculated with input "++ show vs++" and dates"++ show ds diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index 6b4df676..e74c0798 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -769,6 +769,14 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f toRational amt else 0.0 + IrrOfBond bondName -> + case getBondByName t True bondName of + Nothing -> Left $ "[query Irr of bond]Failed to find bond by name"++ bondName + Just bnd -> + let + (ds,vs) = L.bondCashflow bnd + in + A.calcIRR ds vs CustomData s d -> case custom t of diff --git a/src/Types.hs b/src/Types.hs index f7cb4d75..9cfed84b 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -741,6 +741,7 @@ data DealStats = CurrentBondBalance | PoolFactor (Maybe [PoolId]) | BondWaRate [BondName] | DealStatRate DealStatFields + | IrrOfBond BondName -- Compond type | Factor DealStats Rational | Multiply [DealStats] diff --git a/swagger.json b/swagger.json index b3a88587..c754705e 100644 --- a/swagger.json +++ b/swagger.json @@ -8431,6 +8431,25 @@ "title": "DealStatRate", "type": "object" }, + { + "properties": { + "contents": { + "type": "string" + }, + "tag": { + "enum": [ + "IrrOfBond" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "IrrOfBond", + "type": "object" + }, { "properties": { "contents": { @@ -21312,7 +21331,7 @@ "name": "BSD 3" }, "title": "Hastructure API", - "version": "0.52.1" + "version": "0.52.3" }, "openapi": "3.0.0", "paths": { From b50dfa607524fb32e35ea563733aaf5392894181 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Sat, 28 Feb 2026 15:18:38 +0800 Subject: [PATCH 08/17] expose IsAnyOutstanding --- src/Analytics.hs | 2 +- src/Deal/DealQuery.hs | 5 +++++ src/Types.hs | 7 +++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Analytics.hs b/src/Analytics.hs index c64d5580..cbe63377 100644 --- a/src/Analytics.hs +++ b/src/Analytics.hs @@ -265,7 +265,7 @@ calcIRR _ [] = Left "No cashflow amount" calcIRR [] _ = Left "No cashflow date" calcIRR ds vs | all (>= 0) vs = Left $ "All cashflow can't be all positive:"++ show vs - | all (<= 0) vs = Left $ "All cashflow can't be all negative:"++ show vs + | all (<= 0) vs = return $ -1.0 | all (== 0) vs = Left "All cashflow can't be all zeros" | otherwise = let diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index e74c0798..fa3776f9 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -834,6 +834,11 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap vs <- lookupAndApplies (not . isPaidOff) "Is Outstanding" bns bndMap return $ and vs + IsAnyOutstanding bns -> + do + vs <- lookupAndApplies (not . isPaidOff) "Is Any Outstanding" bns bndMap + return $ or vs + IsFeePaidOff fns -> do vs <- lookupAndApplies isPaidOff "Is Fee Paid Off" fns feeMap diff --git a/src/Types.hs b/src/Types.hs index 9cfed84b..0cb677d5 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -635,6 +635,7 @@ data DealStats = CurrentBondBalance | CumulativePoolDefaultedBalance (Maybe [PoolId]) -- ^ Depreciated, use PoolCumCollection | CumulativePoolRecoveriesBalance (Maybe [PoolId]) -- ^ Depreciated, use PoolCumCollection | CumulativeNetLoss (Maybe [PoolId]) + | PoolAccruedInterest Date (Maybe [PoolId]) | OriginalBondBalance | OriginalBondBalanceOf [BondName] | BondTotalFunding [BondName] @@ -659,6 +660,7 @@ data DealStats = CurrentBondBalance | FutureCurrentSchedulePoolBalance (Maybe [PoolId]) | FutureCurrentSchedulePoolBegBalance (Maybe [PoolId]) | PoolScheduleCfPv PricingMethod (Maybe [PoolId]) + | FuturePoolAccruedInterest Date (Maybe [PoolId]) | FuturePoolScheduleCfPv Date PricingMethod (Maybe [PoolId]) | FutureWaCurrentPoolBalance Date Date (Maybe [PoolId]) | FutureCurrentPoolBegBalance (Maybe [PoolId]) @@ -725,6 +727,7 @@ data DealStats = CurrentBondBalance | IsLiqSupportPaidOff [String] | IsRateSwapPaidOff [String] | IsOutstanding [BondName] + | IsAnyOutstanding [BondName] | HasPassedMaturity [BondName] | TriggersStatus DealCycle String | DealStatBool DealStatFields @@ -1178,6 +1181,7 @@ type family DealStatRtn ds where DealStatRtn (Multiply _) = RtnRate DealStatRtn (Factor _ _) = RtnRate DealStatRtn (PoolWaSpread _) = RtnRate + DealStatRtn (IrrOfBond _) = RtnRate DealStatRtn (IsMostSenior _ _) = RtnBool DealStatRtn (IsPaidOff _) = RtnBool @@ -1189,6 +1193,7 @@ type family DealStatRtn ds where DealStatRtn (TestAny _ _) = RtnBool DealStatRtn (TestAll _ _) = RtnBool DealStatRtn (DealStatBool _) = RtnBool + DealStatRtn (IsAnyOutstanding _) = RtnBool DealStatRtn (Max (s:dss)) = DealStatRtn s DealStatRtn (Min (s:dss)) = DealStatRtn s @@ -1217,6 +1222,7 @@ getDealStatType (Divide ds1 ds2) = RtnRate getDealStatType (Multiply _) = RtnRate getDealStatType (Factor _ _) = RtnRate getDealStatType (PoolWaSpread _) = RtnRate +getDealStatType (IrrOfBond _) = RtnRate getDealStatType (CurrentPoolBorrowerNum _) = RtnInt getDealStatType (MonthsTillMaturity _) = RtnInt @@ -1226,6 +1232,7 @@ getDealStatType (DealStatInt _) = RtnInt getDealStatType (IsMostSenior _ _) = RtnBool getDealStatType IsPaidOff {} = RtnBool getDealStatType IsOutstanding {} = RtnBool +getDealStatType (IsAnyOutstanding _) = RtnBool getDealStatType HasPassedMaturity {} = RtnBool getDealStatType (TriggersStatus _ _)= RtnBool getDealStatType (IsDealStatus _)= RtnBool From 71cda000401deb709f8b65e3eb1c22217829b3b3 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Mon, 2 Mar 2026 00:15:11 +0800 Subject: [PATCH 09/17] WIP: poolAccrual formula --- src/AssetClass/AssetBase.hs | 5 + src/AssetClass/StudentLoan.hs | 42 +++ src/Deal.hs | 2 +- src/Deal/DealBase.hs | 64 ++-- src/Deal/DealDate.hs | 13 +- src/Deal/DealMod.hs | 6 +- src/Deal/DealQuery.hs | 182 +++++++----- src/Deal/DealRun.hs | 190 ++++++------ src/Pool.hs | 25 +- src/Types.hs | 538 +++++++++++++++++----------------- 10 files changed, 581 insertions(+), 486 deletions(-) create mode 100644 src/AssetClass/StudentLoan.hs diff --git a/src/AssetClass/AssetBase.hs b/src/AssetClass/AssetBase.hs index ef344671..54632a5a 100644 --- a/src/AssetClass/AssetBase.hs +++ b/src/AssetClass/AssetBase.hs @@ -200,6 +200,11 @@ data Mortgage = Mortgage OriginalInfo Balance IRate RemainTerms (Maybe BorrowerN | ScheduleMortgageFlow Date [CF.TsRow] DatePattern deriving (Show,Generic,Eq,Ord) +data StudentLoan = StudentLoan OriginalInfo Balance IRate RemainTerms Status + | DUMMY3 + deriving (Show,Generic,Eq,Ord) + + type FixRatePortion = (Rate, IRate) type FloatRatePortion = (Rate, IRate, Spread, Index) type ScheduleBalance = (Date, Balance) diff --git a/src/AssetClass/StudentLoan.hs b/src/AssetClass/StudentLoan.hs new file mode 100644 index 00000000..1878d2a4 --- /dev/null +++ b/src/AssetClass/StudentLoan.hs @@ -0,0 +1,42 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DeriveGeneric #-} + +module AssetClass.Mortgage + (projectMortgageFlow,projectScheduleFlow,updateOriginDate,getOriginInfo + ,buildARMrates) + where + +import qualified Data.Time as T +import qualified Cashflow as CF +import qualified Assumptions as A +import Asset as Ast +import Types +import Lib +import Util +import DateUtil +import InterestRate as IR + +import qualified Data.Map as Map +import Data.List +import Data.Ratio +import Data.Maybe +import GHC.Generics +import Data.Aeson hiding (json) +import Language.Haskell.TH +import Data.Aeson.TH +import Data.Aeson.Types + +import AssetClass.AssetBase +import AssetClass.AssetCashflow +import Debug.Trace +import Assumptions (AssetPerfAssumption(MortgageAssump)) +import GHC.Float.RealFracMethods (truncateFloatInteger) +import Cashflow (extendTxns) +import Control.Lens hiding (element) +import Control.Lens.TH +import qualified Data.DList as DL + +debug = flip trace + + diff --git a/src/Deal.hs b/src/Deal.hs index f5c9c5b4..29cf542e 100644 --- a/src/Deal.hs +++ b/src/Deal.hs @@ -437,7 +437,7 @@ patchRuntimeBal balMap pt = pt getInits :: Ast.Asset a => S.Set ExpectReturn -> TestDeal a -> Maybe AP.ApplyAssumptionType -> Maybe AP.NonPerfAssumption - -> Either String (TestDeal a,[ActionOnDate], Map.Map PoolId CF.PoolCashflow, Map.Map PoolId CF.PoolCashflow) + -> Either String (TestDeal a,[ActionOnDate], Map.Map PoolId CF.PoolCashflow, Map.Map PoolId CF.PoolCashflow) getInits er t@TestDeal{accounts = accMap, fees=feeMap,pool=thePool,status=status,bonds=bndMap,stats=_stats,dates=dealDates} mAssumps mNonPerfAssump = let expandInspect sd ed (AP.InspectPt dp ds) = [ InspectDS _d [ds] | _d <- genSerialDatesTill2 II sd dp ed ] diff --git a/src/Deal/DealBase.hs b/src/Deal/DealBase.hs index a6a7efea..e485915a 100644 --- a/src/Deal/DealBase.hs +++ b/src/Deal/DealBase.hs @@ -7,16 +7,16 @@ {-# LANGUAGE FlexibleInstances #-} module Deal.DealBase (TestDeal(..),SPV(..),dealBonds,dealFees,dealAccounts,dealPool,PoolType(..),getIssuanceStats - ,getAllAsset,getAllAssetList,getAllCollectedFrame,getLatestCollectFrame,getAllCollectedTxns - ,getIssuanceStatsConsol,getAllCollectedTxnsList - ,getPoolIds,getBondByName, UnderlyingDeal(..),viewDealAllBonds,DateDesp(..),ActionOnDate(..) - ,sortActionOnDate,dealBondGroups - ,viewDealBondsByNames,poolTypePool,viewBondsInMap,bondGroupsBonds - ,increaseBondPaidPeriod,increasePoolCollectedPeriod - ,DealStatFields(..),getDealStatInt,isPreClosing,populateDealDates - ,bondTraversal,findBondByNames,updateBondInMap,traverseBondMap,traverseBondMapByFn - ,_MultiPool,_ResecDeal,uDealFutureCf,uDealFutureScheduleCf - ) + ,getAllAsset,getAllAssetList,getAllCollectedFrame,getLatestCollectFrame,getAllCollectedTxns + ,getIssuanceStatsConsol,getAllCollectedTxnsList + ,getPoolIds,getBondByName, UnderlyingDeal(..),viewDealAllBonds,DateDesp(..),ActionOnDate(..) + ,sortActionOnDate,dealBondGroups + ,viewDealBondsByNames,poolTypePool,viewBondsInMap,bondGroupsBonds + ,increaseBondPaidPeriod,increasePoolCollectedPeriod + ,DealStatFields(..),getDealStatInt,isPreClosing,populateDealDates + ,bondTraversal,findBondByNames,updateBondInMap,traverseBondMap,traverseBondMapByFn + ,_MultiPool,_ResecDeal,uDealFutureCf,uDealFutureScheduleCf + ) where import qualified Accounts as A import qualified Ledger as LD @@ -366,30 +366,30 @@ instance SPV (TestDeal a) where getFeeByName t fns = case fns of - Nothing -> fees t - Just _fns -> Map.filterWithKey (\k _ -> S.member k (S.fromList _fns)) (fees t) + Nothing -> fees t + Just _fns -> Map.filterWithKey (\k _ -> S.member k (S.fromList _fns)) (fees t) getAccountByName t ans = case ans of - Nothing -> accounts t - Just _ans -> Map.filterWithKey (\k _ -> S.member k (S.fromList _ans)) (accounts t) + Nothing -> accounts t + Just _ans -> Map.filterWithKey (\k _ -> S.member k (S.fromList _ans)) (accounts t) isResec t = case pool t of - ResecDeal _ -> True - _ -> False + ResecDeal _ -> True + _ -> False getOustandingBal t@TestDeal{ bonds = bndMap, fees= feeMap, liqProvider = mliqMap, rateSwap = rsMap} - = let - bndBal = sum $ getOutstandingAmount <$> Map.elems bndMap - feeBal = sum $ getOutstandingAmount <$> Map.elems feeMap - lqBalace m - | not (Map.null m) = sum $ getOutstandingAmount <$> Map.elems m - | otherwise = 0 - rsBalance m - | not (Map.null m) = sum $ getOutstandingAmount <$> Map.elems m - | otherwise = 0 - in - bndBal + feeBal + lqBalace (fromMaybe Map.empty mliqMap) + rsBalance (fromMaybe Map.empty rsMap) + = let + bndBal = sum $ getOutstandingAmount <$> Map.elems bndMap + feeBal = sum $ getOutstandingAmount <$> Map.elems feeMap + lqBalace m + | not (Map.null m) = sum $ getOutstandingAmount <$> Map.elems m + | otherwise = 0 + rsBalance m + | not (Map.null m) = sum $ getOutstandingAmount <$> Map.elems m + | otherwise = 0 + in + bndBal + feeBal + lqBalace (fromMaybe Map.empty mliqMap) + rsBalance (fromMaybe Map.empty rsMap) isPreClosing :: TestDeal a -> Bool isPreClosing t@TestDeal{ status = PreClosing _ } = True @@ -400,12 +400,12 @@ isPreClosing _ = False viewDealAllBonds :: TestDeal a -> [L.Bond] viewDealAllBonds d = let - bs = Map.elems (bonds d) - view a@(L.Bond {} ) = [a] - view a@(L.BondGroup bMap _) = Map.elems bMap - view a@(L.MultiIntBond {}) = [a] + bs = Map.elems (bonds d) + view a@(L.Bond {} ) = [a] + view a@(L.BondGroup bMap _) = Map.elems bMap + view a@(L.MultiIntBond {}) = [a] in - concat $ view <$> bs + concat $ view <$> bs -- ^ flatten all bonds/bond groups in a map viewBondsInMap :: TestDeal a -> Map.Map String L.Bond diff --git a/src/Deal/DealDate.hs b/src/Deal/DealDate.hs index 0947643e..ccb0718a 100644 --- a/src/Deal/DealDate.hs +++ b/src/Deal/DealDate.hs @@ -3,7 +3,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE LambdaCase #-} -module Deal.DealDate (DealDates,getClosingDate,getFirstPayDate,getLastPayDate) +module Deal.DealDate (DealDates,getClosingDate,getFirstPayDate,getLastPayDate,getCutoffDate) where import qualified Data.Map as Map @@ -15,11 +15,13 @@ class DealDates a where getClosingDate :: a -> Either String Date getFirstPayDate :: a -> Date getLastPayDate :: a -> Either String Date + getCutoffDate :: a -> Date instance DealDates DateDesp where getClosingDate (GenericDates m) = case Map.lookup ClosingDate m of Just (SingletonDate x) -> Right x Nothing -> Left $ "ClosingDate not found in GenericDates"++show m + getClosingDate (PreClosingDates _ x _ _ _ _) = Right x getClosingDate (CurrentDates (_,cd) _ _ _ _ ) = Right cd -- getClosingDate (AccruedGenericDates m) = getClosingDate (GenericDates m) @@ -37,5 +39,14 @@ instance DealDates DateDesp where getFirstPayDate (GenericDates m) = case Map.lookup FirstPayDate m of Just (SingletonDate x) -> x Nothing -> error "FirstPayDate not found in GenericDates" + + getCutoffDate (GenericDates m) = case Map.lookup CutoffDate m of + Just (SingletonDate x) -> x + Nothing -> error "CutoffDate not found in GenericDates" + getCutoffDate (PreClosingDates cutoff _ _ _ _ _ ) = cutoff + getCutoffDate (CurrentDates (cutoff,_) _ _ _ _ ) = cutoff + -- getCutoffDate (AccruedGenericDates m) = getCutoff + + -- getFirstPayDate (AccruedGenericDates m) = getFirstPayDate (GenericDates m) diff --git a/src/Deal/DealMod.hs b/src/Deal/DealMod.hs index 1e0b3e24..83d876bf 100644 --- a/src/Deal/DealMod.hs +++ b/src/Deal/DealMod.hs @@ -67,8 +67,8 @@ debug = flip trace data AdjStrategy = ScaleBySpread - | ScaleByFactor - deriving (Show,Generic) + | ScaleByFactor + deriving (Show,Generic) data ModifyType = AddSpreadToBonds BondName | SlideBalances BondName BondName @@ -94,7 +94,7 @@ modDeal (SlideBalances bn1 bn2) r d@DB.TestDeal {DB.bonds = bndMap} leftBal = mulBR totalBalance (toRational r) -- `debug` ("split ratio" ++ show r) rightBal = totalBalance - leftBal bndMap' = DB.updateBondInMap bn1 (L.adjustBalance leftBal) $ - DB.updateBondInMap bn2 (L.adjustBalance rightBal) bndMap -- `debug` ("leftBal: " ++ show leftBal ++ ", rightBal: " ++ show rightBal ) + DB.updateBondInMap bn2 (L.adjustBalance rightBal) bndMap -- `debug` ("leftBal: " ++ show leftBal ++ ", rightBal: " ++ show rightBal ) in d {DB.bonds = bndMap'} diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index fa3776f9..c9023a8c 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -12,6 +12,7 @@ module Deal.DealQuery (queryDealBool ,patchDateToStats,patchDatesToStats,testPre where import Deal.DealBase +import Deal.DealDate import Types import qualified Asset as P import qualified AssetClass.AssetBase as AB @@ -91,15 +92,16 @@ calcBondTargetBalance t d (L.BondGroup bMap mPt) = | queryDealBool t (IsPaidOff _bnds) d == Right False -> return $ getValOnByDate _target d | otherwise -> Left $ "Calculate paid off bonds failed"++ show _bnds ++" in calc target balance" Just (L.AmtByPeriod pc) -> case getValFromPerCurve pc Past Inc (fromMaybe 0 (getDealStatInt t BondPaidPeriod)) of - Just v -> return v - Nothing -> Left "Failed to find value in calcTargetBalance" + Just v -> return v + Nothing -> Left "Failed to find value in calcTargetBalance" _ -> Left $ "not support principal type for bond group"++ show mPt calcBondTargetBalance t d b = case L.bndType b of L.Sequential -> return 0 - L.Lockout ld | d >= ld -> return 0 - | otherwise -> return $ L.bndBalance b + L.Lockout ld + | d >= ld -> return 0 + | otherwise -> return $ L.bndBalance b L.Z | all (==True) (isPaidOff <$> (Map.elems (Map.delete (L.bndName b) (bonds t)))) -> return 0 | otherwise -> return $ L.bndBalance b @@ -126,6 +128,7 @@ patchDateToStats d t LastFeePaid fns -> FeesPaidAt d fns LastBondPrinPaid bns -> BondsPrinPaidAt d bns BondBalanceGap bn -> BondBalanceGapAt d bn + PoolAccruedInterest mPns -> FuturePoolAccruedInterest d mPns ReserveGap ans -> ReserveGapAt d ans ReserveExcess ans -> ReserveExcessAt d ans Sum _ds -> Sum $ map (patchDateToStats d) _ds @@ -213,7 +216,7 @@ queryCompound :: P.Asset a => TestDeal a -> Date -> DealStats -> Either ErrorRep queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=feeMap, pool=pt} d s = case s of - Sum _s -> sum <$> sequenceA [ queryCompound t d __s | __s <- _s] + Sum _s -> sum <$> sequenceA [ queryCompound t d __s | __s <- _s ] Substract dss -> queryCompound t d (Subtract dss) Subtract [] -> Left $ "Date:"++show d++"Can not subtract empty list" Subtract (ds:dss) -> @@ -510,15 +513,15 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f in do curPoolBalM <- sequenceA $ - Map.mapWithKey - (\k v -> queryCompound t d (FutureCurrentPoolBalance (Just [k]))) - pStat -- `debug` ("date"++show d++"Pool stats collection: "++ show pStat) + Map.mapWithKey + (\k v -> queryCompound t d (FutureCurrentPoolBalance (Just [k]))) + pStat -- `debug` ("date"++show d++"Pool stats collection: "++ show pStat) let poolStat = Map.mapWithKey - (\k v -> - case v of - Just _v -> sum $ CF.lookupSource _v <$> ps - Nothing -> sum $ CF.lookupSourceM (fromRational (curPoolBalM Map.! k)) Nothing <$> ps) - pStat -- `debug` ("date"++show d++"query pool current pool stat 2" ++ show pStat ) + (\k v -> + case v of + Just _v -> sum $ CF.lookupSource _v <$> ps + Nothing -> sum $ CF.lookupSourceM (fromRational (curPoolBalM Map.! k)) Nothing <$> ps) + pStat -- `debug` ("date"++show d++"query pool current pool stat 2" ++ show pStat ) return $ sum $ Map.elems $ toRational <$> poolStat -- `debug` ("query pool current stats"++ show poolStat) FuturePoolScheduleCfPv asOfDay pm mPns -> @@ -539,8 +542,8 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f scheduleBal <- queryCompound t d (FutureCurrentSchedulePoolBegBalance mPns) curBal <- queryCompound t d (FutureCurrentPoolBalance mPns) let factor = case scheduleBal of - 0.00 -> 0 - _ -> curBal / scheduleBal -- `debug` ("cur Bal"++show curBal ++">> sheduleBal"++ show scheduleBal) + 0.00 -> 0 + _ -> curBal / scheduleBal -- `debug` ("cur Bal"++show curBal ++">> sheduleBal"++ show scheduleBal) let cfForPv = (`mulBR` factor) <$> txnsCfs -- `debug` (">>> factor"++ show factor) let pvs = case pm of PvRate r -> uncurry (A.pv2 r asOfDay) <$> zip txnsDs cfForPv @@ -548,32 +551,32 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f return $ toRational $ sum pvs BondsIntPaidAt d bns -> - let - stmts = map L.bndStmt $ viewDealBondsByNames t bns - ex s = case s of - Nothing -> 0 - Just (Statement txns) - -> sum $ map getTxnAmt $ - filter (\y -> case getTxnComment y of - (PayInt _ ) -> True - _ -> False) $ - filter (\x -> d == getDate x) (DL.toList txns) - in - Right . toRational $ sum $ map ex stmts + let + stmts = map L.bndStmt $ viewDealBondsByNames t bns + ex s = case s of + Nothing -> 0 + Just (Statement txns) + -> sum $ map getTxnAmt $ + filter (\y -> case getTxnComment y of + (PayInt _ ) -> True + _ -> False) $ + filter (\x -> d == getDate x) (DL.toList txns) + in + Right . toRational $ sum $ map ex stmts BondsPrinPaidAt d bns -> - let - stmts = map L.bndStmt $ viewDealBondsByNames t bns - ex s = case s of - Nothing -> 0 - Just (Statement txns) - -> sum $ map getTxnAmt $ - filter (\y -> case getTxnComment y of - (PayPrin _ ) -> True - _ -> False) $ - filter (\x -> d == getDate x) (DL.toList txns) - in - Right . toRational $ sum $ map ex stmts + let + stmts = map L.bndStmt $ viewDealBondsByNames t bns + ex s = case s of + Nothing -> 0 + Just (Statement txns) + -> sum $ map getTxnAmt $ + filter (\y -> case getTxnComment y of + (PayPrin _ ) -> True + _ -> False) $ + filter (\x -> d == getDate x) (DL.toList txns) + in + Right . toRational $ sum $ map ex stmts FeeTxnAmtBy d fns mCmt -> let @@ -695,9 +698,9 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f Just liqProviderM -> let xs = [ case (CE.liqCredit liq) of - Unlimit -> 0 - ByAvailAmount v -> v - | (k,liq) <- Map.assocs liqProviderM , S.member k (S.fromList lqNames) ] + Unlimit -> 0 + ByAvailAmount v -> v + | (k,liq) <- Map.assocs liqProviderM , S.member k (S.fromList lqNames) ] in Right . toRational $ sum xs @@ -705,20 +708,20 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f case liqProvider t of Nothing -> Left $ "Date:"++show d++"No Liquidation Provider modeled when looking for " ++ show s Just liqProviderM -> Right . toRational $ - sum $ [ CE.liqBalance liq | (k,liq) <- Map.assocs liqProviderM - , S.member k (S.fromList lqNames) ] + sum $ [ CE.liqBalance liq | (k,liq) <- Map.assocs liqProviderM + , S.member k (S.fromList lqNames) ] RateCapNet rcName -> case rateCap t of - Nothing -> Left $ "Date:"++show d++"No Rate Cap modeled when looking for " ++ show s - Just rm -> case Map.lookup rcName rm of - Nothing -> Left $ "Date:"++show d++"No Rate Cap modeled when looking for " ++ show s - Just rc -> Right . toRational $ H.rcNetCash rc - + Nothing -> Left $ "Date:"++show d++"No Rate Cap modeled when looking for " ++ show s + Just rm -> case Map.lookup rcName rm of + Nothing -> Left $ "Date:"++show d++"No Rate Cap modeled when looking for " ++ show s + Just rc -> Right . toRational $ H.rcNetCash rc + RateSwapNet rsName -> case rateCap t of - Nothing -> Left $ "Date:"++show d++"No Rate Swap modeled when looking for " ++ show s - Just rm -> case Map.lookup rsName rm of - Nothing -> Left $ "Date:"++show d++"No Rate Swap modeled when looking for " ++ show s - Just rc -> Right . toRational $ H.rcNetCash rc + Nothing -> Left $ "Date:"++show d++"No Rate Swap modeled when looking for " ++ show s + Just rm -> case Map.lookup rsName rm of + Nothing -> Left $ "Date:"++show d++"No Rate Swap modeled when looking for " ++ show s + Just rc -> Right . toRational $ H.rcNetCash rc WeightedAvgCurrentBondBalance d1 d2 bns -> Right . toRational $ @@ -778,6 +781,37 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f in A.calcIRR ds vs + FuturePoolAccruedInterest d mPns -> + -- TODO https://github.com/absbox/Hastructure/issues/316 + -- TODO it won't work for bonds as underlying assets(resec deals) + let + pCf::(Map.Map PoolId (Maybe CF.TsRow)) = getLatestCollectFrame t mPns -- `debug` ("mPns"++ show mPns) + + accrueIntFn :: PoolId -> Maybe CF.TsRow -> Balance + accrueIntFn pid Nothing = + case pt of + MultiPool poolMap -> + case Map.lookup pid poolMap of + Just pool -> case Pl.getIssuanceField pool RuntimeCurrentPoolBalance of + Right bal -> + let + accrueRate = 0.0 + sd = getCutoffDate (dates t) + in + mulBR bal ((yearCountFraction DC_ACT_365F sd d) * accrueRate) + Left _ -> 0.0 + Nothing -> 0.0 + -- TODO add support for resec deal + _ -> 0.0 + accrueIntFn _ (Just (CF.MortgageFlow sd bal _ _ _ _ _ _ r _ _ _)) = mulBR bal (yearCountFraction DC_ACT_365F sd d * (toRational r)) + accrueIntFn _ (Just (CF.MortgageDelinqFlow sd bal _ _ _ _ _ _ _ r _ _ _)) = mulBR bal (yearCountFraction DC_ACT_365F sd d * (toRational r)) + accrueIntFn _ (Just (CF.LoanFlow sd bal _ _ _ _ _ _ r _)) = mulBR bal (yearCountFraction DC_ACT_365F sd d * (toRational r)) + accrueIntFn _ (Just r) = 0.0 + + in + Right . toRational $ sum $ Map.elems $ Map.mapWithKey accrueIntFn pCf + + CustomData s d -> case custom t of Nothing -> Left $ "Date:"++show d++"No Custom data to query" ++ show s @@ -808,11 +842,11 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap TriggersStatus dealcycle tName -> case trgs of Just _trgsM -> case Map.lookup dealcycle _trgsM of - Nothing -> Left ("Date:"++show d++"no trigger cycle for this deal" ++ show dealcycle) - Just triggerMatCycle -> - case Map.lookup tName triggerMatCycle of - Nothing -> Left ("Date:"++show d++"no trigger for this deal" ++ show tName ++ " in cycle " ++ show triggerMatCycle) - Just trigger -> return $ Trg.trgStatus trigger + Nothing -> Left ("Date:"++show d++"no trigger cycle for this deal" ++ show dealcycle) + Just triggerMatCycle -> + case Map.lookup tName triggerMatCycle of + Nothing -> Left ("Date:"++show d++"no trigger for this deal" ++ show tName ++ " in cycle " ++ show triggerMatCycle) + Just trigger -> return $ Trg.trgStatus trigger Nothing -> Left $ "Date:"++show d++"no trigger for this deal" IsMostSenior bn bns -> @@ -858,17 +892,17 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap testRate <- queryCompound t d ds let r = toRational r return $ case cmp of - G -> testRate > r - GE -> testRate >= r - L -> testRate < r - LE -> testRate <= r - E -> testRate == r + G -> testRate > r + GE -> testRate >= r + L -> testRate < r + LE -> testRate <= r + E -> testRate == r HasPassedMaturity bns -> do - bMap <- selectInMap "Bond Pass Maturity" bns bndMap - let oustandingBnds = Map.filter (not . isPaidOff) bMap - ms <- sequenceA $ (\bn -> queryCompound t d (MonthsTillMaturity bn)) <$> L.bndName <$> oustandingBnds - return $ all (<= 0) ms + bMap <- selectInMap "Bond Pass Maturity" bns bndMap + let oustandingBnds = Map.filter (not . isPaidOff) bMap + ms <- sequenceA $ (\bn -> queryCompound t d (MonthsTillMaturity bn)) <$> L.bndName <$> oustandingBnds + return $ all (<= 0) ms IsDealStatus st -> return $ status t == st @@ -905,8 +939,8 @@ testPre d t p = q <- (queryCompound t d (ps s)) return $ toCmp cmp q (toRational amt) -- `debug` (show d++"rate"++show (queryDealRate t (ps s))++"amt"++show amt) IfInt cmp s amt -> do - q <- (queryCompound t d (ps s)) - return $ toCmp cmp q (toRational amt) + q <- (queryCompound t d (ps s)) + return $ toCmp cmp q (toRational amt) -- Integer test IfIntIn s iset -> do @@ -929,8 +963,8 @@ testPre d t p = IfDateIn ds -> return $ d `elem` ds IfCurve cmp s _ts -> do - q <- (queryCompound t d (ps s)) - return $ toCmp cmp q (getValByDate _ts Inc d) + q <- (queryCompound t d (ps s)) + return $ toCmp cmp q (getValByDate _ts Inc d) IfRateCurve cmp s _ts -> do v <- (queryCompound t d (ps s)) return $ (toCmp cmp) v (getValByDate _ts Inc d) IfByPeriodCurve cmp sVal sSelect pc -> @@ -956,9 +990,9 @@ testPre d t p = q <- (queryDealBool t s d) return q If2 cmp s1 s2 -> do - q1 <- (queryCompound t d (ps s1)) - q2 <- (queryCompound t d (ps s2)) - return (toCmp cmp q1 q2) + q1 <- (queryCompound t d (ps s1)) + q2 <- (queryCompound t d (ps s2)) + return (toCmp cmp q1 q2) IfRate2 cmp s1 s2 -> do q1 <- (queryCompound t d (ps s1)) q2 <- (queryCompound t d (ps s2)) diff --git a/src/Deal/DealRun.hs b/src/Deal/DealRun.hs index 8927dcb9..55f9a2e6 100644 --- a/src/Deal/DealRun.hs +++ b/src/Deal/DealRun.hs @@ -46,15 +46,15 @@ debug = flip trace -- ^ execute effects of trigger: making changes to deal -- TODO seems position of arugments can be changed : f :: a -> b -> m a => f:: b -> a -> m a runEffects :: Ast.Asset a => (TestDeal a, RunContext a, [ActionOnDate], DL.DList ResultComponent) -> Date -> TriggerEffect - -> Either String (TestDeal a, RunContext a, [ActionOnDate], DL.DList ResultComponent) + -> Either String (TestDeal a, RunContext a, [ActionOnDate], DL.DList ResultComponent) runEffects (t@TestDeal{accounts = accMap, fees = feeMap ,status=st, bonds = bondMap, pool=pt ,collects = collRules}, rc, actions, logs) d te = case te of DealStatusTo _ds -> return (t {status = _ds}, rc, actions, logs) DoAccrueFee fns -> do - newFeeList <- traverse (calcDueFee t d) $ (feeMap Map.!) <$> fns - let newFeeMap = Map.fromList (zip fns newFeeList) <> feeMap - return (t {fees = newFeeMap}, rc, actions, logs) + newFeeList <- traverse (calcDueFee t d) $ (feeMap Map.!) <$> fns + let newFeeMap = Map.fromList (zip fns newFeeList) <> feeMap + return (t {fees = newFeeMap}, rc, actions, logs) ChangeReserveBalance accName rAmt -> return (t {accounts = Map.adjust (set A.accTypeLens (Just rAmt)) accName accMap } @@ -205,8 +205,8 @@ runTriggers (t@TestDeal{status=oldStatus, triggers = Just trgM},rc, actions) d d do let trgsMap = Map.findWithDefault Map.empty dcycle trgM let trgsToTest = Map.filter - (\trg -> (not (trgStatus trg) || trgStatus trg && trgCurable trg)) - trgsMap + (\trg -> (not (trgStatus trg) || trgStatus trg && trgCurable trg)) + trgsMap triggeredTrgs <- mapM (testTrigger t d) trgsToTest let triggeredEffects = [ trgEffects _trg | _trg <- Map.elems triggeredTrgs, (trgStatus _trg) ] (newDeal, newRc, newActions, logsFromTrigger) <- foldM (`runEffects` d) (t,rc,actions, DL.empty) triggeredEffects @@ -214,9 +214,9 @@ runTriggers (t@TestDeal{status=oldStatus, triggers = Just trgM},rc, actions) d d let newLogs = DL.fromList [DealStatusChangeTo d oldStatus newStatus "By trigger"| newStatus /= oldStatus] -- `debug` (">>"++show d++"trigger : new st"++ show newStatus++"old st"++show oldStatus) let newTriggers = Map.union triggeredTrgs trgsMap return (newDeal {triggers = Just (Map.insert dcycle newTriggers trgM)} - , newRc - , newActions - , DL.append newLogs logsFromTrigger) -- `debug` ("New logs from trigger"++ show d ++">>>"++show newLogs) + , newRc + , newActions + , DL.append newLogs logsFromTrigger) -- `debug` ("New logs from trigger"++ show d ++">>>"++show newLogs) appendCollectedCF :: Ast.Asset a => Date -> TestDeal a -> Map.Map PoolId CF.PoolCashflow -> TestDeal a -- ^ append cashflow frame (consolidate by a date) into deals collected pool @@ -288,17 +288,17 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= ,waterfall=waterfallM,name=dealName,pool=pt,stats=_stat} poolFlowMap (Just (ad:ads)) rates calls rAssump log | futureCashToCollectFlag && (queryCompound t (getDate ad) AllAccBalance == Right 0) && (dStatus /= Revolving) && (dStatus /= Warehousing Nothing) --TODO need to use prsim here to cover all warehouse status - = let - runContext = RunContext poolFlowMap rAssump rates --- `debug` ("ending at date " ++ show (getDate ad)) - endingLog = EndRun (Just (getDate ad)) "No Pool Cashflow/All Account is zero/Not revolving" - endingDate = getDate ad - in - if Map.member W.CleanUp waterfallM then - do - (finalDeal,RunContext newPoolFlowMap _ _,newLogs) <- foldM (performActionWrap endingDate) (t,runContext,log) cleanUpActions - return (finalDeal, DL.concat [newLogs, DL.fromList [RunningWaterfall endingDate W.CleanUp, endingLog] ] , newPoolFlowMap) - else - return (t , DL.snoc log endingLog, poolFlowMap) + = let + runContext = RunContext poolFlowMap rAssump rates --- `debug` ("ending at date " ++ show (getDate ad)) + endingLog = EndRun (Just (getDate ad)) "No Pool Cashflow/All Account is zero/Not revolving" + endingDate = getDate ad + in + if Map.member W.CleanUp waterfallM then + do + (finalDeal,RunContext newPoolFlowMap _ _,newLogs) <- foldM (performActionWrap endingDate) (t,runContext,log) cleanUpActions + return (finalDeal, DL.concat [newLogs, DL.fromList [RunningWaterfall endingDate W.CleanUp, endingLog] ] , newPoolFlowMap) + else + return (t , DL.snoc log endingLog, poolFlowMap) | otherwise = case ad of -- TODO : need to seperate waterfall execution in pool collection @@ -325,11 +325,11 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= ResecDeal dMap -> ResecDeal $ (over (mapped . uDealFutureScheduleCf . _Just . CF.cashflowTxn) cutFutureCf) dMap let runContext = RunContext outstandingFlow rAssump rates - (dRunWithTrigger0, rc1, ads2, newLogs0) <- runTriggers (dAfterDeposit {pool = newPt},runContext,ads) d EndCollection + (dRunWithTrigger0, rc1, ads2, newLogs0) <- runTriggers (dAfterDeposit {pool = newPt}, runContext, ads) d EndCollection let eopActionsLog = DL.fromList [ RunningWaterfall d W.EndOfPoolCollection | Map.member W.EndOfPoolCollection waterfallM ] - let waterfallToExe = Map.findWithDefault [] W.EndOfPoolCollection (waterfall t) + let waterfallToExe = Map.findWithDefault [] W.EndOfPoolCollection waterfallM (dAfterAction,rc2,newLogs) <- foldM (performActionWrap d) (dRunWithTrigger0 ,rc1 ,log ) waterfallToExe - (dRunWithTrigger1,rc3,ads3,newLogs1) <- runTriggers (dAfterAction,rc2,ads2) d EndCollectionWF + (dRunWithTrigger1, rc3, ads3, newLogs1) <- runTriggers (dAfterAction, rc2, ads2) d EndCollectionWF run (increasePoolCollectedPeriod dRunWithTrigger1 ) (runPoolFlow rc3) (Just ads3) @@ -378,9 +378,9 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= (dealAfterCleanUp, rc_, newLogWaterfall_) <- foldM (performActionWrap d) (dRunWithTrigger0, rc1,log) cleanUpActions endingLogs <- Rpt.patchFinancialReports dealAfterCleanUp d newLogWaterfall_ return (dealAfterCleanUp - , DL.concat [logsBeforeDist,endingLogs,DL.fromList (newStLogs++[EndRun (Just d) "Clean Up"])] - , runPoolFlow rc_ - ) + , DL.concat [logsBeforeDist,endingLogs,DL.fromList (newStLogs++[EndRun (Just d) "Clean Up"])] + , runPoolFlow rc_ + ) else -- Non-Clean Up Waterfall Actions do @@ -426,18 +426,18 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= Nothing -> run t poolFlowMap (Just ads) rates calls rAssump log (Just mLiqProvider) -> let -- update credit - newLiqMap = Map.adjust (updateLiqProvider t d) liqName mLiqProvider - in - run (t{liqProvider = Just newLiqMap}) poolFlowMap (Just ads) rates calls rAssump log + newLiqMap = Map.adjust (updateLiqProvider t d) liqName mLiqProvider + in + run (t{liqProvider = Just newLiqMap}) poolFlowMap (Just ads) rates calls rAssump log ResetLiqProviderRate d liqName -> case liqProvider t of Nothing -> run t poolFlowMap (Just ads) rates calls rAssump log (Just mLiqProvider) -> let -- update rate - newLiqMap = Map.adjust (updateLiqProviderRate t d (fromMaybe [] rates)) liqName mLiqProvider - in - run (t{liqProvider = Just newLiqMap}) poolFlowMap (Just ads) rates calls rAssump log + newLiqMap = Map.adjust (updateLiqProviderRate t d (fromMaybe [] rates)) liqName mLiqProvider + in + run (t{liqProvider = Just newLiqMap}) poolFlowMap (Just ads) rates calls rAssump log DealClosed d -> let @@ -447,8 +447,8 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= in do newSt <- case dStatus of - (PreClosing st) -> return st - _ -> Left $ "DealClosed action is not in PreClosing status but got"++ show dStatus + (PreClosing st) -> return st + _ -> Left $ "DealClosed action is not in PreClosing status but got"++ show dStatus (newDeal, newRc, newLog) <- foldM (performActionWrap d) (t, rc, log) w -- `debug` ("ClosingDay Action:"++show w) run newDeal{status=newSt} (runPoolFlow newRc) (Just ads) rates calls rAssump (DL.concat [newLog, DL.fromList ([DealStatusChangeTo d (PreClosing newSt) newSt "By Deal Close"]++logForClosed)]) -- `debug` ("new st at closing"++ show newSt) @@ -539,9 +539,9 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newAccMap <- adjustM (\a@(A.Account _ _ (Just (A.InvestmentAccount idx spd dp dp1 lastDay _)) _ _) -> do - newRate <- AP.lookupRate (fromMaybe [] rates) (idx,spd) d - let accWithNewInt = A.depositInt d a - return accWithNewInt { A.accInterest = Just (A.InvestmentAccount idx spd dp dp1 lastDay newRate)}) + newRate <- AP.lookupRate (fromMaybe [] rates) (idx,spd) d + 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 @@ -570,9 +570,10 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= MakeWhole d spd walTbl -> let - schedulePoolFlowMap = case pt of - MultiPool pMap -> Map.map (view (P.poolFutureScheduleCf._Just._1) ) pMap - ResecDeal uDealMap -> Map.map (view (uDealFutureScheduleCf . _Just)) uDealMap + schedulePoolFlowMap = + case pt of + MultiPool pMap -> Map.map (view (P.poolFutureScheduleCf._Just._1) ) pMap + ResecDeal uDealMap -> Map.map (view (uDealFutureScheduleCf . _Just)) uDealMap in do factor <- liftA2 @@ -627,7 +628,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= IssueBond d Nothing bGroupName accName bnd mBal mRate -> - run t poolFlowMap (Just ((IssueBond d (Just (Always True)) bGroupName accName bnd mBal mRate):ads)) rates calls rAssump log + run t poolFlowMap (Just ((IssueBond d (Just (Always True)) bGroupName accName bnd mBal mRate):ads)) rates calls rAssump log IssueBond d (Just p) bGroupName accName bnd mBal mRate -> do @@ -637,56 +638,57 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= True -> let newBndName = L.bndName bnd in - do - newBalance <- case mBal of - Just _q -> queryCompound t d (patchDateToStats d _q) - Nothing -> Right . toRational $ L.originBalance (L.bndOriginInfo bnd) - newRate <- case mRate of - Just _q -> queryCompound t d (patchDateToStats d _q) - Nothing -> return $ L.originRate (L.bndOriginInfo bnd) - let newBonds = case Map.lookup bGroupName bndMap of - Nothing -> bndMap - Just L.Bond {} -> bndMap - Just (L.BondGroup bndGrpMap pt) -> let - bndOInfo = (L.bndOriginInfo bnd) {L.originDate = d, L.originRate = newRate, L.originBalance = fromRational newBalance } - bndToInsert = bnd {L.bndOriginInfo = bndOInfo, - L.bndDueIntDate = Just d, - L.bndLastIntPay = Just d, - L.bndLastPrinPay = Just d, - L.bndRate = fromRational newRate, - L.bndBalance = fromRational newBalance} - in - Map.insert bGroupName - (L.BondGroup (Map.insert newBndName bndToInsert bndGrpMap) pt) - bndMap - - let issuanceProceeds = fromRational newBalance - let newAcc = Map.adjust (A.deposit issuanceProceeds d (IssuanceProceeds newBndName)) - accName - accMap - run t{bonds = newBonds, accounts = newAcc} poolFlowMap (Just ads) rates calls rAssump log + do + newBalance <- case mBal of + Just _q -> queryCompound t d (patchDateToStats d _q) + Nothing -> Right . toRational $ L.originBalance (L.bndOriginInfo bnd) + newRate <- case mRate of + Just _q -> queryCompound t d (patchDateToStats d _q) + Nothing -> return $ L.originRate (L.bndOriginInfo bnd) + let newBonds = case Map.lookup bGroupName bndMap of + Nothing -> bndMap + Just L.Bond {} -> bndMap + Just (L.BondGroup bndGrpMap pt) -> let + bndOInfo = (L.bndOriginInfo bnd) {L.originDate = d, L.originRate = newRate, L.originBalance = fromRational newBalance } + bndToInsert = bnd {L.bndOriginInfo = bndOInfo, + L.bndDueIntDate = Just d, + L.bndLastIntPay = Just d, + L.bndLastPrinPay = Just d, + L.bndRate = fromRational newRate, + L.bndBalance = fromRational newBalance} + in + Map.insert bGroupName + (L.BondGroup (Map.insert newBndName bndToInsert bndGrpMap) pt) + bndMap + + let issuanceProceeds = fromRational newBalance + let newAcc = Map.adjust + (A.deposit issuanceProceeds d (IssuanceProceeds newBndName)) + accName + accMap + run t{bonds = newBonds, accounts = newAcc} poolFlowMap (Just ads) rates calls rAssump log RefiBondRate d accName bName iInfo -> let - lstDate = getDate (last ads) - isResetActionEvent (ResetBondRate _ bName ) = False - isResetActionEvent _ = True - filteredAds = filter isResetActionEvent ads - newRate = L.getBeginRate iInfo + lstDate = getDate (last ads) + isResetActionEvent (ResetBondRate _ bName ) = False + isResetActionEvent _ = True + filteredAds = filter isResetActionEvent ads + newRate = L.getBeginRate iInfo in - do - nBnd <- calcDueInt t d $ bndMap Map.! bName - let dueIntToPay = L.getTotalDueInt nBnd - let acc = accMap Map.! accName - let actualPayout = min (A.accBalance acc) dueIntToPay - bnd1 <- pay d (DueTotalOf [DueInterest Nothing, DueArrears]) actualPayout nBnd - let newBnd = set L.bndIntLens iInfo bnd1 - let resetDates = L.buildRateResetDates newBnd d lstDate - let bResetActions = [ ResetBondRate d' bName | d' <- resetDates ] - newAccMap <- adjustM (draw d actualPayout (PayInt [bName])) accName accMap - let newBndMap = Map.insert bName (newBnd {L.bndRate = newRate, L.bndDueIntDate = Just d ,L.bndLastIntPay = Just d}) bndMap - let newAds = sortBy sortActionOnDate $ filteredAds ++ bResetActions - run t{bonds = newBndMap, accounts = newAccMap} poolFlowMap (Just newAds) rates calls rAssump log - + do + nBnd <- calcDueInt t d $ bndMap Map.! bName + let dueIntToPay = L.getTotalDueInt nBnd + let acc = accMap Map.! accName + let actualPayout = min (A.accBalance acc) dueIntToPay + bnd1 <- pay d (DueTotalOf [DueInterest Nothing, DueArrears]) actualPayout nBnd + let newBnd = set L.bndIntLens iInfo bnd1 + let resetDates = L.buildRateResetDates newBnd d lstDate + let bResetActions = [ ResetBondRate d' bName | d' <- resetDates ] + newAccMap <- adjustM (draw d actualPayout (PayInt [bName])) accName accMap + let newBndMap = Map.insert bName (newBnd {L.bndRate = newRate, L.bndDueIntDate = Just d ,L.bndLastIntPay = Just d}) bndMap + let newAds = sortBy sortActionOnDate $ filteredAds ++ bResetActions + run t{bonds = newBndMap, accounts = newAccMap} poolFlowMap (Just newAds) rates calls rAssump log + RefiBond d accName bnd -> Left "Undefined action: RefiBond" TestCall d -> @@ -699,7 +701,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= True -> let runContext = RunContext poolFlowMap rAssump rates - newStLogs + newStLogs | null cleanUpActions = DL.fromList [DealStatusChangeTo d dStatus Called "by Date-Based Call"] | otherwise = DL.fromList [DealStatusChangeTo d dStatus Called "by Date-Based Call", RunningWaterfall d W.CleanUp] in @@ -712,11 +714,11 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= _ -> run t poolFlowMap (Just ads) rates calls rAssump log StopRunTest d pres -> - do - flags::[Bool] <- sequenceA $ [ (testPre d t pre) | pre <- pres ] - case all id flags of - True -> return (t, DL.snoc log (EndRun (Just d) ("Stop Run Test by:"++ show (zip pres flags))), poolFlowMap) - _ -> run t poolFlowMap (Just ads) rates calls rAssump log + do + flags::[Bool] <- sequenceA $ [ (testPre d t pre) | pre <- pres ] + case all id flags of + True -> return (t, DL.snoc log (EndRun (Just d) ("Stop Run Test by:"++ show (zip pres flags))), poolFlowMap) + _ -> run t poolFlowMap (Just ads) rates calls rAssump log _ -> Left $ "Failed to match action on Date"++ show ad diff --git a/src/Pool.hs b/src/Pool.hs index 68143e39..4f65918b 100644 --- a/src/Pool.hs +++ b/src/Pool.hs @@ -49,13 +49,13 @@ import Debug.Trace debug = flip trace -data Pool a = Pool {assets :: [a] -- ^ a list of assets in the pool - ,futureCf :: Maybe CF.PoolCashflow -- ^ collected cashflow from the assets in the pool - ,futureScheduleCf :: Maybe CF.PoolCashflow -- ^ collected un-stressed cashflow - ,asOfDate :: Date -- ^ include cashflow after this date - ,issuanceStat :: Maybe (Map.Map CutoffFields Balance) -- ^ cutoff balance of pool - ,extendPeriods :: Maybe DatePattern -- ^ dates for extend pool collection - } deriving (Show, Generic, Ord, Eq) +data Pool a = Pool {assets :: [a] -- ^ a list of assets in the pool + ,futureCf :: Maybe CF.PoolCashflow -- ^ collected cashflow from the assets in the pool + ,futureScheduleCf :: Maybe CF.PoolCashflow -- ^ collected un-stressed cashflow + ,asOfDate :: Date -- ^ include cashflow after this date + ,issuanceStat :: Maybe (Map.Map CutoffFields Balance) -- ^ cutoff balance of pool + ,extendPeriods :: Maybe DatePattern -- ^ dates for extend pool collection + } deriving (Show, Generic, Ord, Eq) makeLensesFor [("futureCf","futureCfLens"),("futureScheduleCf","futureScheduleCfLens")] ''Pool @@ -81,11 +81,11 @@ poolIssuanceStat = lens getter setter -- | get stats of pool -getIssuanceField :: Pool a -> CutoffFields -> Either String Balance +getIssuanceField :: Pool a -> CutoffFields -> Either ErrorRep Balance getIssuanceField p@Pool{issuanceStat = Just m} s = case Map.lookup s m of Just r -> Right r - Nothing -> Left $ "Faile dto find field "++ show s ++ "in pool issuance " ++ show m + Nothing -> Left $ "Failed to find field "++ show s ++ "in pool issuance " ++ show m getIssuanceField Pool{issuanceStat = Nothing} s = Left $ "There is no pool stats to lookup:" ++ show s @@ -178,9 +178,10 @@ pricingPoolFlow d pool@Pool{ futureCf = Just (mCollectedCf,_), issuanceStat = mS in fromMaybe 0 (CF.tsCumDefaultBal lastTxn) - fromMaybe 0 (CF.tsCumRecoveriesBal lastTxn) - fromMaybe 0 (CF.tsCumLossBal lastTxn) - currentPerformingBal = case mStat of - Nothing -> 0 - Just stat -> Map.findWithDefault 0 RuntimeCurrentPoolBalance stat + currentPerformingBal = + case mStat of + Nothing -> 0 + Just stat -> Map.findWithDefault 0 RuntimeCurrentPoolBalance stat in case pm of diff --git a/src/Types.hs b/src/Types.hs index 0cb677d5..321bc8cc 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -218,28 +218,28 @@ data DateType = ClosingDate -- ^ deal closing day data DatePattern = MonthEnd - | QuarterEnd - | YearEnd - | MonthFirst - | QuarterFirst - | MidYear - | YearFirst - | MonthDayOfYear Int Int -- T.MonthOfYear T.DayOfMonth - | DayOfMonth Int -- T.DayOfMonth - | SemiAnnual (Int, Int) (Int, Int) - | CustomDate [Date] - | SingletonDate Date - | DaysInYear [(Int, Int)] -- MM/DD - | EveryNMonth Date Int - | Weekday Int - | AllDatePattern [DatePattern] - | StartsExclusive Date DatePattern -- TODO depricated - | StartsAt CutoffType Date DatePattern - | EndsAt CutoffType Date DatePattern - | Exclude DatePattern [DatePattern] - | OffsetBy DatePattern Int - -- | DayOfWeek Int -- T.DayOfWeek - deriving (Show, Eq, Generic, Ord, Read) + | QuarterEnd + | YearEnd + | MonthFirst + | QuarterFirst + | MidYear + | YearFirst + | MonthDayOfYear Int Int -- T.MonthOfYear T.DayOfMonth + | DayOfMonth Int -- T.DayOfMonth + | SemiAnnual (Int, Int) (Int, Int) + | CustomDate [Date] + | SingletonDate Date + | DaysInYear [(Int, Int)] -- MM/DD + | EveryNMonth Date Int + | Weekday Int + | AllDatePattern [DatePattern] + | StartsExclusive Date DatePattern -- TODO depricated + | StartsAt CutoffType Date DatePattern + | EndsAt CutoffType Date DatePattern + | Exclude DatePattern [DatePattern] + | OffsetBy DatePattern Int + -- | DayOfWeek Int -- T.DayOfWeek + deriving (Show, Eq, Generic, Ord, Read) data Period = Daily @@ -289,11 +289,11 @@ instance (Read PoolId) where data Cmp = G -- ^ Greater than - | GE -- ^ Greater Equal than - | L -- ^ Less than - | LE -- ^ Less Equal than - | E -- ^ Equals to - deriving (Generic,Eq,Ord,Read) + | GE -- ^ Greater Equal than + | L -- ^ Less than + | LE -- ^ Less Equal than + | E -- ^ Equals to + deriving (Generic,Eq,Ord,Read) instance Show Cmp where show :: Cmp -> String @@ -383,19 +383,19 @@ 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 - | EE -- ^ exclude either start date and end date - | NO_IE -- ^ no handling on start date and end date - deriving (Show,Eq,Read,Generic,Ord) + | IE -- ^ include start date ,but not end date + | EI -- ^ exclude start date but include end date + | EE -- ^ exclude either start date and end date + | NO_IE -- ^ no handling on start date and end date + deriving (Show,Eq,Read,Generic,Ord) data CutoffType = Inc | Exc deriving (Show,Ord,Read,Generic,Eq) data DateDirection = Future - | Past - deriving (Show,Read,Generic) + | Past + deriving (Show,Read,Generic) data InvestorAction = Buy | Sell @@ -484,13 +484,13 @@ data Ts = FloatCurve [TsPoint Rational] data Direction = Up - | Down - deriving (Show,Read,Generic,Eq,Ord) + | Down + deriving (Show,Read,Generic,Eq,Ord) -- ^ direction of the transaction, in terms of the book keeping data BookDirection = Credit - | Debit - deriving (Show,Ord, Eq,Read, Generic) + | Debit + deriving (Show,Ord, Eq,Read, Generic) type DueInt = Balance @@ -498,11 +498,11 @@ type DuePremium = Balance type DueIoI = Balance data DealCycle = EndCollection -- ^ | collection period collection action , waterfall action - | EndCollectionWF -- ^ | collection period collection action , waterfall action - | BeginDistributionWF -- ^ | collection period collection action , waterfall action - | EndDistributionWF -- ^ | collection period collection action , waterfall action - | InWF -- ^ | collection period collection action , waterfall action - deriving (Show, Ord, Eq, Read, Generic) + | EndCollectionWF -- ^ | collection period collection action , waterfall action + | BeginDistributionWF -- ^ | collection period collection action , waterfall action + | EndDistributionWF -- ^ | collection period collection action , waterfall action + | InWF -- ^ | collection period collection action , waterfall action + deriving (Show, Ord, Eq, Read, Generic) -- ^ different status of the deal data DealStatus = DealAccelerated (Maybe Date) -- ^ Deal is accelerated status with optinal accerlerated date @@ -517,15 +517,15 @@ data DealStatus = DealAccelerated (Maybe Date) -- ^ Deal is accelerated sta -- ^ 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 - | PV IRate Rate -- ^ discount factor, recovery pct on default - | PVCurve Ts -- ^ [CF] Pricing cashflow with a Curve - | PvRate IRate -- ^ [CF] Pricing cashflow with a constant rate - | PvWal Ts - | PvByRef DealStats -- ^ [CF] Pricing cashflow with a ref rate - | Custom Rate -- ^ custom amount - deriving (Show, Eq ,Generic, Read, Ord) + | BalanceFactor2 Rate Rate Rate -- ^ [balance] by performing/delinq/default factor + | DefaultedBalance Rate -- ^ [balance] only liquidate defaulted balance + | PV IRate Rate -- ^ discount factor, recovery pct on default + | PVCurve Ts -- ^ [CF] Pricing cashflow with a Curve + | PvRate IRate -- ^ [CF] Pricing cashflow with a constant rate + | PvWal Ts + | PvByRef DealStats -- ^ [CF] Pricing cashflow with a ref rate + | Custom Rate -- ^ custom amount + deriving (Show, Eq ,Generic, Read, Ord) -- ^ pricing methods for bonds data BondPricingMethod = BondBalanceFactor Rate @@ -536,43 +536,43 @@ data BondPricingMethod = BondBalanceFactor Rate -- ^ condition which can be evaluated to a boolean value data Pre = IfZero DealStats - | If Cmp DealStats Balance - | IfRate Cmp DealStats Micro - | IfCurve Cmp DealStats Ts - | IfByPeriodCurve Cmp DealStats DealStats (PerCurve Balance) - | IfRateCurve Cmp DealStats Ts - | IfRateByPeriodCurve Cmp DealStats DealStats (PerCurve Rate) - | IfIntCurve Cmp DealStats Ts - -- Integer - | IfInt Cmp DealStats Int - | IfIntBetween DealStats RangeType Int Int - | IfIntIn DealStats [Int] - -- Dates - | IfDate Cmp Date - | IfDateBetween RangeType Date Date - | IfDateIn Dates - -- Bool - | IfBool DealStats Bool - -- compare deal status - | If2 Cmp DealStats DealStats - | IfRate2 Cmp DealStats DealStats - | IfInt2 Cmp DealStats DealStats - -- | IfRateCurve DealStats Cmp Ts - | IfDealStatus DealStatus - | Always Bool - | IfNot Pre - | Any [Pre] - | All [Pre] -- ^ - deriving (Show,Generic,Eq,Ord,Read) + | If Cmp DealStats Balance + | IfRate Cmp DealStats Micro + | IfCurve Cmp DealStats Ts + | IfByPeriodCurve Cmp DealStats DealStats (PerCurve Balance) + | IfRateCurve Cmp DealStats Ts + | IfRateByPeriodCurve Cmp DealStats DealStats (PerCurve Rate) + | IfIntCurve Cmp DealStats Ts + -- Integer + | IfInt Cmp DealStats Int + | IfIntBetween DealStats RangeType Int Int + | IfIntIn DealStats [Int] + -- Dates + | IfDate Cmp Date + | IfDateBetween RangeType Date Date + | IfDateIn Dates + -- Bool + | IfBool DealStats Bool + -- compare deal status + | If2 Cmp DealStats DealStats + | IfRate2 Cmp DealStats DealStats + | IfInt2 Cmp DealStats DealStats + -- | IfRateCurve DealStats Cmp Ts + | IfDealStatus DealStatus + | Always Bool + | IfNot Pre + | Any [Pre] + | All [Pre] -- ^ + deriving (Show,Generic,Eq,Ord,Read) data Table a b = ThresholdTable [(a,b)] - deriving (Show,Eq,Ord,Read,Generic) + deriving (Show,Eq,Ord,Read,Generic) data ActionType = ActionResetRate -- ^ reset interest rate from curve | ActionAccrue -- ^ accrue liablity - deriving (Show,Eq,Ord,Read,Generic) + deriving (Show,Eq,Ord,Read,Generic) -- ^ comment of the transaction in the accounts data TxnComment = PayInt [BondName] @@ -614,13 +614,13 @@ type FeeArrears = Balance -- ^ transaction record in each entity data Txn = BondTxn Date Balance Interest Principal IRate Cash DueInt DueIoI (Maybe Float) TxnComment -- ^ bond transaction record for interest and principal - | AccTxn Date Balance Amount TxnComment -- ^ account transaction record - | ExpTxn Date FeeDue Amount FeeArrears TxnComment -- ^ expense transaction record - | SupportTxn Date SupportAvailType Balance DueInt DuePremium Cash TxnComment -- ^ liquidity provider transaction record - | IrsTxn Date Balance Amount IRate IRate Balance TxnComment -- ^ interest swap transaction record - | EntryTxn Date (BookDirection,Balance) (BookDirection,Amount) TxnComment -- ^ ledger book entry - | TrgTxn Date Bool TxnComment - deriving (Show, Generic, Eq, Read) + | AccTxn Date Balance Amount TxnComment -- ^ account transaction record + | ExpTxn Date FeeDue Amount FeeArrears TxnComment -- ^ expense transaction record + | SupportTxn Date SupportAvailType Balance DueInt DuePremium Cash TxnComment -- ^ liquidity provider transaction record + | IrsTxn Date Balance Amount IRate IRate Balance TxnComment -- ^ interest swap transaction record + | EntryTxn Date (BookDirection,Balance) (BookDirection,Amount) TxnComment -- ^ ledger book entry + | TrgTxn Date Bool TxnComment + deriving (Show, Generic, Eq, Read) data DealStatFields = PoolCollectedPeriod @@ -629,143 +629,143 @@ data DealStatFields = PoolCollectedPeriod -- ^ different types of deal stats data DealStats = CurrentBondBalance - | CurrentPoolBalance (Maybe [PoolId]) - | CurrentPoolBegBalance (Maybe [PoolId]) - | CurrentPoolDefaultedBalance - | CumulativePoolDefaultedBalance (Maybe [PoolId]) -- ^ Depreciated, use PoolCumCollection - | CumulativePoolRecoveriesBalance (Maybe [PoolId]) -- ^ Depreciated, use PoolCumCollection - | CumulativeNetLoss (Maybe [PoolId]) - | PoolAccruedInterest Date (Maybe [PoolId]) - | OriginalBondBalance - | OriginalBondBalanceOf [BondName] - | BondTotalFunding [BondName] - | OriginalPoolBalance (Maybe [PoolId]) - | UseCustomData String - | PoolCumCollection [PoolSource] (Maybe [PoolId]) - | PoolCumCollectionTill Int [PoolSource] (Maybe [PoolId]) - | PoolCurCollection [PoolSource] (Maybe [PoolId]) - | PoolCollectionStats Int [PoolSource] (Maybe [PoolId]) - | PoolWaSpread (Maybe [PoolId]) - | AllAccBalance - | AccBalance [AccName] - | LedgerBalance [String] - | LedgerBalanceBy BookDirection [String] - | LedgerTxnAmt [String] (Maybe TxnComment) - | ReserveBalance [AccName] - | ReserveGap [AccName] - | ReserveExcess [AccName] - | ReserveGapAt Date [AccName] - | ReserveExcessAt Date [AccName] - | FutureCurrentPoolBalance (Maybe [PoolId]) - | FutureCurrentSchedulePoolBalance (Maybe [PoolId]) - | FutureCurrentSchedulePoolBegBalance (Maybe [PoolId]) - | PoolScheduleCfPv PricingMethod (Maybe [PoolId]) - | FuturePoolAccruedInterest Date (Maybe [PoolId]) - | FuturePoolScheduleCfPv Date PricingMethod (Maybe [PoolId]) - | FutureWaCurrentPoolBalance Date Date (Maybe [PoolId]) - | FutureCurrentPoolBegBalance (Maybe [PoolId]) - | FutureCurrentBondBalance Date - | CurrentBondBalanceOf [BondName] - | BondIntPaidAt Date BondName - | BondsIntPaidAt Date [BondName] - | BondPrinPaidAt Date BondName - | BondsPrinPaidAt Date [BondName] - | BondBalanceTarget [BondName] - | BondBalanceGap BondName - | BondBalanceGapAt Date BondName - | BondDuePrin [BondName] - | BondReturn BondName Balance [TsPoint Amount] - | FeePaidAmt [FeeName] - | FeeTxnAmt [FeeName] (Maybe TxnComment) - | BondTxnAmt [BondName] (Maybe TxnComment) - | AccTxnAmt [AccName] (Maybe TxnComment) - | FeeTxnAmtBy Date [FeeName] (Maybe TxnComment) - | BondTxnAmtBy Date [BondName] (Maybe TxnComment) - | AccTxnAmtBy Date [AccName] (Maybe TxnComment) - | FeesPaidAt Date [FeeName] - | CurrentDueBondInt [BondName] - | CurrentDueBondIntAt Int [BondName] - | CurrentDueBondIntOverInt [BondName] - | CurrentDueBondIntOverIntAt Int [BondName] - | CurrentDueBondIntTotal [BondName] - | CurrentDueBondIntTotalAt Int [BondName] - | CurrentDueFee [FeeName] - | LastBondIntPaid [BondName] - | LastBondPrinPaid [BondName] - | LastFeePaid [FeeName] - | LiqCredit [String] - | LiqBalance [String] - | RateCapNet String - | RateSwapNet String - | BondBalanceHistory Date Date - | PoolCollectionHistory PoolSource Date Date (Maybe [PoolId]) - | UnderlyingBondBalance (Maybe [BondName]) - | WeightedAvgCurrentPoolBalance Date Date (Maybe [PoolId]) - | WeightedAvgCurrentBondBalance Date Date [BondName] - | WeightedAvgOriginalPoolBalance Date Date (Maybe [PoolId]) - | WeightedAvgOriginalBondBalance Date Date [BondName] - | CustomData String Date - | DealStatBalance DealStatFields - -- analytical query - | AmountRequiredForTargetIRR Double BondName - -- integer type - | CurrentPoolBorrowerNum (Maybe [PoolId]) - | FutureCurrentPoolBorrowerNum Date (Maybe [PoolId]) - | ProjCollectPeriodNum - | MonthsTillMaturity BondName - | DealStatInt DealStatFields - | ActiveBondNum - -- boolean type - | TestRate DealStats Cmp Micro - | TestAny Bool [DealStats] - | TestAll Bool [DealStats] - | TestNot DealStats - | IsDealStatus DealStatus - | IsMostSenior BondName [BondName] - | IsPaidOff [BondName] - | IsFeePaidOff [String] - | IsLiqSupportPaidOff [String] - | IsRateSwapPaidOff [String] - | IsOutstanding [BondName] - | IsAnyOutstanding [BondName] - | HasPassedMaturity [BondName] - | TriggersStatus DealCycle String - | DealStatBool DealStatFields - -- rate type - | PoolWaRate (Maybe PoolId) - | BondRate BondName - | CumulativeNetLossRatio (Maybe [PoolId]) - | FutureCurrentBondFactor Date - | FutureCurrentPoolFactor Date (Maybe [PoolId]) - | BondFactor - | BondFactorOf BondName - | CumulativePoolDefaultedRate (Maybe [PoolId]) - | CumulativePoolDefaultedRateTill Int (Maybe [PoolId]) - | PoolFactor (Maybe [PoolId]) - | BondWaRate [BondName] - | DealStatRate DealStatFields - | IrrOfBond BondName - -- Compond type - | Factor DealStats Rational - | Multiply [DealStats] - | Max [DealStats] - | Min [DealStats] - | Sum [DealStats] - | Substract [DealStats] - | Subtract [DealStats] - | Excess [DealStats] - | Avg [DealStats] - | AvgRatio [DealStats] - | Divide DealStats DealStats - | DivideRatio DealStats DealStats - | Constant Rational - | FloorAndCap DealStats DealStats DealStats - | FloorWith DealStats DealStats - | FloorWithZero DealStats - | CapWith DealStats DealStats - | Abs DealStats - | Round DealStats (RoundingBy Rational) - deriving (Show,Eq,Ord,Read,Generic) + | CurrentPoolBalance (Maybe [PoolId]) + | CurrentPoolBegBalance (Maybe [PoolId]) + | CurrentPoolDefaultedBalance + | CumulativePoolDefaultedBalance (Maybe [PoolId]) -- ^ Depreciated, use PoolCumCollection + | CumulativePoolRecoveriesBalance (Maybe [PoolId]) -- ^ Depreciated, use PoolCumCollection + | CumulativeNetLoss (Maybe [PoolId]) + | PoolAccruedInterest (Maybe [PoolId]) + | OriginalBondBalance + | OriginalBondBalanceOf [BondName] + | BondTotalFunding [BondName] + | OriginalPoolBalance (Maybe [PoolId]) + | UseCustomData String + | PoolCumCollection [PoolSource] (Maybe [PoolId]) + | PoolCumCollectionTill Int [PoolSource] (Maybe [PoolId]) + | PoolCurCollection [PoolSource] (Maybe [PoolId]) + | PoolCollectionStats Int [PoolSource] (Maybe [PoolId]) + | PoolWaSpread (Maybe [PoolId]) + | AllAccBalance + | AccBalance [AccName] + | LedgerBalance [String] + | LedgerBalanceBy BookDirection [String] + | LedgerTxnAmt [String] (Maybe TxnComment) + | ReserveBalance [AccName] + | ReserveGap [AccName] + | ReserveExcess [AccName] + | ReserveGapAt Date [AccName] + | ReserveExcessAt Date [AccName] + | FutureCurrentPoolBalance (Maybe [PoolId]) + | FutureCurrentSchedulePoolBalance (Maybe [PoolId]) + | FutureCurrentSchedulePoolBegBalance (Maybe [PoolId]) + | PoolScheduleCfPv PricingMethod (Maybe [PoolId]) + | FuturePoolAccruedInterest Date (Maybe [PoolId]) + | FuturePoolScheduleCfPv Date PricingMethod (Maybe [PoolId]) + | FutureWaCurrentPoolBalance Date Date (Maybe [PoolId]) + | FutureCurrentPoolBegBalance (Maybe [PoolId]) + | FutureCurrentBondBalance Date + | CurrentBondBalanceOf [BondName] + | BondIntPaidAt Date BondName + | BondsIntPaidAt Date [BondName] + | BondPrinPaidAt Date BondName + | BondsPrinPaidAt Date [BondName] + | BondBalanceTarget [BondName] + | BondBalanceGap BondName + | BondBalanceGapAt Date BondName + | BondDuePrin [BondName] + | BondReturn BondName Balance [TsPoint Amount] + | FeePaidAmt [FeeName] + | FeeTxnAmt [FeeName] (Maybe TxnComment) + | BondTxnAmt [BondName] (Maybe TxnComment) + | AccTxnAmt [AccName] (Maybe TxnComment) + | FeeTxnAmtBy Date [FeeName] (Maybe TxnComment) + | BondTxnAmtBy Date [BondName] (Maybe TxnComment) + | AccTxnAmtBy Date [AccName] (Maybe TxnComment) + | FeesPaidAt Date [FeeName] + | CurrentDueBondInt [BondName] + | CurrentDueBondIntAt Int [BondName] + | CurrentDueBondIntOverInt [BondName] + | CurrentDueBondIntOverIntAt Int [BondName] + | CurrentDueBondIntTotal [BondName] + | CurrentDueBondIntTotalAt Int [BondName] + | CurrentDueFee [FeeName] + | LastBondIntPaid [BondName] + | LastBondPrinPaid [BondName] + | LastFeePaid [FeeName] + | LiqCredit [String] + | LiqBalance [String] + | RateCapNet String + | RateSwapNet String + | BondBalanceHistory Date Date + | PoolCollectionHistory PoolSource Date Date (Maybe [PoolId]) + | UnderlyingBondBalance (Maybe [BondName]) + | WeightedAvgCurrentPoolBalance Date Date (Maybe [PoolId]) + | WeightedAvgCurrentBondBalance Date Date [BondName] + | WeightedAvgOriginalPoolBalance Date Date (Maybe [PoolId]) + | WeightedAvgOriginalBondBalance Date Date [BondName] + | CustomData String Date + | DealStatBalance DealStatFields + -- analytical query + | AmountRequiredForTargetIRR Double BondName + -- integer type + | CurrentPoolBorrowerNum (Maybe [PoolId]) + | FutureCurrentPoolBorrowerNum Date (Maybe [PoolId]) + | ProjCollectPeriodNum + | MonthsTillMaturity BondName + | DealStatInt DealStatFields + | ActiveBondNum + -- boolean type + | TestRate DealStats Cmp Micro + | TestAny Bool [DealStats] + | TestAll Bool [DealStats] + | TestNot DealStats + | IsDealStatus DealStatus + | IsMostSenior BondName [BondName] + | IsPaidOff [BondName] + | IsFeePaidOff [String] + | IsLiqSupportPaidOff [String] + | IsRateSwapPaidOff [String] + | IsOutstanding [BondName] + | IsAnyOutstanding [BondName] + | HasPassedMaturity [BondName] + | TriggersStatus DealCycle String + | DealStatBool DealStatFields + -- rate type + | PoolWaRate (Maybe PoolId) + | BondRate BondName + | CumulativeNetLossRatio (Maybe [PoolId]) + | FutureCurrentBondFactor Date + | FutureCurrentPoolFactor Date (Maybe [PoolId]) + | BondFactor + | BondFactorOf BondName + | CumulativePoolDefaultedRate (Maybe [PoolId]) + | CumulativePoolDefaultedRateTill Int (Maybe [PoolId]) + | PoolFactor (Maybe [PoolId]) + | BondWaRate [BondName] + | DealStatRate DealStatFields + | IrrOfBond BondName + -- Compond type + | Factor DealStats Rational + | Multiply [DealStats] + | Max [DealStats] + | Min [DealStats] + | Sum [DealStats] + | Substract [DealStats] + | Subtract [DealStats] + | Excess [DealStats] + | Avg [DealStats] + | AvgRatio [DealStats] + | Divide DealStats DealStats + | DivideRatio DealStats DealStats + | Constant Rational + | FloorAndCap DealStats DealStats DealStats + | FloorWith DealStats DealStats + | FloorWithZero DealStats + | CapWith DealStats DealStats + | Abs DealStats + | Round DealStats (RoundingBy Rational) + deriving (Show,Eq,Ord,Read,Generic) data EvalExpr a where @@ -798,7 +798,7 @@ data EvalExpr a where -- readsPrec _ "EvalSum" = [(EvalSum [],"")] -- readsPrec _ "CurrentBondBalance'" = [(CurrentBondBalance', "")] -- readsPrec _ _ = error "EvalExpr: not implemented for other constructors" - + preHasTrigger :: Pre -> [(DealCycle,String)] preHasTrigger (IfBool (TriggersStatus dc tName) _) = [(dc,tName)] preHasTrigger (Any ps) = concatMap preHasTrigger ps @@ -807,14 +807,14 @@ preHasTrigger _ = [] data Limit = DuePct Rate -- ^ up to % of total amount due - | DueCapAmt Balance -- ^ up to $ amount - | KeepBalAmt DealStats -- ^ pay till a certain amount remains in an account - | DS DealStats -- ^ transfer with limit described by a `DealStats` - | RemainBalPct Rate -- ^ pay till remain balance equals to a percentage of `stats` - | TillTarget -- ^ transfer amount which make target account up reach reserve balanace - | TillSource -- ^ transfer amount out till source account down back to reserve balance - | Multiple Limit Float -- ^ factor of a limit - deriving (Show,Ord,Eq,Read,Generic) + | DueCapAmt Balance -- ^ up to $ amount + | KeepBalAmt DealStats -- ^ pay till a certain amount remains in an account + | DS DealStats -- ^ transfer with limit described by a `DealStats` + | RemainBalPct Rate -- ^ pay till remain balance equals to a percentage of `stats` + | TillTarget -- ^ transfer amount which make target account up reach reserve balanace + | TillSource -- ^ transfer amount out till source account down back to reserve balance + | Multiple Limit Float -- ^ factor of a limit + deriving (Show,Ord,Eq,Read,Generic) data HowToPay = ByProRata | BySequential @@ -843,16 +843,16 @@ data CashflowReport = CashflowReport { data Threshold = Below - | EqBelow - | Above - | EqAbove - deriving (Show,Eq,Ord,Read,Generic) + | EqBelow + | Above + | EqAbove + deriving (Show,Eq,Ord,Read,Generic) data SplitType = EqToLeft -- if equal, the element belongs to left - | EqToRight -- if equal, the element belongs to right - | EqToLeftKeepOne - | EqToLeftKeepOnes - deriving (Show, Eq, Generic) + | EqToRight -- if equal, the element belongs to right + | EqToLeftKeepOne + | EqToLeftKeepOnes + deriving (Show, Eq, Generic) -- ^ deal level cumulative statistics data CutoffFields = IssuanceBalance -- ^ pool issuance balance @@ -873,11 +873,11 @@ data CutoffFields = IssuanceBalance -- ^ pool issuance balance data PriceResult = PriceResult Valuation PerFace WAL Duration Convexity AccruedInterest [Txn] - | AssetPrice Valuation WAL Duration Convexity AccruedInterest - | OASResult PriceResult [Valuation] Spread - | ZSpread Spread - | IrrResult IRR [Txn] - deriving (Show, Eq, Generic) + | AssetPrice Valuation WAL Duration Convexity AccruedInterest + | OASResult PriceResult [Valuation] Spread + | ZSpread Spread + | IrrResult IRR [Txn] + deriving (Show, Eq, Generic) makePrisms ''PriceResult @@ -912,12 +912,12 @@ class Liable lb where data DueType = DueInterest (Maybe Int) -- ^ interest due - | DuePrincipal -- ^ principal due - | DueFee -- ^ fee due - | DueResidual -- ^ residual - | DueArrears -- ^ something that is not paid in the past - | DueTotalOf [DueType] -- ^ a combination of above with sequence - deriving (Show, Eq, Generic) + | DuePrincipal -- ^ principal due + | DueFee -- ^ fee due + | DueResidual -- ^ residual + | DueArrears -- ^ something that is not paid in the past + | DueTotalOf [DueType] -- ^ a combination of above with sequence + deriving (Show, Eq, Generic) class Accruable ac where @@ -1023,20 +1023,20 @@ data ActionWhen = EndOfPoolCollection -- ^ waterfall executed at the data ResultComponent = CallAt Date -- ^ the date when deal called - | DealStatusChangeTo Date DealStatus DealStatus String -- ^ record when & why status changed - | BondOutstanding String Balance Balance -- ^ when deal ends,calculate oustanding principal balance - | BondOutstandingInt String Balance Balance -- ^ when deal ends,calculate oustanding interest due - | InspectBal Date DealStats Balance -- ^ A bal value from inspection - | InspectInt Date DealStats Int -- ^ A int value from inspection - | InspectRate Date DealStats Micro -- ^ A rate value from inspection - | InspectBool Date DealStats Bool -- ^ A bool value from inspection - | RunningWaterfall Date ActionWhen -- ^ running waterfall at a date - | FinancialReport StartDate EndDate BalanceSheetReport CashflowReport - | InspectWaterfall Date (Maybe String) [DealStats] [String] - | ErrorMsg String - | WarningMsg String - | EndRun (Maybe Date) String -- ^ end of run with a message - deriving (Show, Generic,Eq) + | DealStatusChangeTo Date DealStatus DealStatus String -- ^ record when & why status changed + | BondOutstanding String Balance Balance -- ^ when deal ends,calculate oustanding principal balance + | BondOutstandingInt String Balance Balance -- ^ when deal ends,calculate oustanding interest due + | InspectBal Date DealStats Balance -- ^ A bal value from inspection + | InspectInt Date DealStats Int -- ^ A int value from inspection + | InspectRate Date DealStats Micro -- ^ A rate value from inspection + | InspectBool Date DealStats Bool -- ^ A bool value from inspection + | RunningWaterfall Date ActionWhen -- ^ running waterfall at a date + | FinancialReport StartDate EndDate BalanceSheetReport CashflowReport + | InspectWaterfall Date (Maybe String) [DealStats] [String] + | ErrorMsg String + | WarningMsg String + | EndRun (Maybe Date) String -- ^ end of run with a message + deriving (Show, Generic,Eq) makePrisms ''ResultComponent @@ -1133,7 +1133,7 @@ parseTxn t = case tagName of "SettleOut" -> return $ SwapOutSettle contents "PurchaseAsset" -> let sv = T.splitOn (T.pack ",") $ T.pack contents - in + in return $ PurchaseAsset (read (T.unpack (sv!!0))::String) (read (T.unpack (sv!!1))::Balance) "TxnDirection" -> return $ TxnDirection (read contents::BookDirection) From b08304de8130f16faa7fdd7c21515502a9510253 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Tue, 3 Mar 2026 00:54:15 +0800 Subject: [PATCH 10/17] clean up --- app/Main.hs | 4 +-- src/Analytics.hs | 31 ++++++++++-------- src/Asset.hs | 16 +++++----- src/Assumptions.hs | 2 +- src/Cashflow.hs | 13 ++++---- src/Deal.hs | 45 ++++++-------------------- src/Deal/DealBase.hs | 7 ++-- src/Liability.hs | 54 ------------------------------- src/Reports.hs | 2 +- swagger.json | 76 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 123 insertions(+), 127 deletions(-) diff --git a/app/Main.hs b/app/Main.hs index d2c37144..a751afab 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -400,8 +400,8 @@ evalRootFindStop (BalanceFormula ds targetBal) (dt,collectedFlow,logs,_,osPflow) _date = case find (\(EndRun d msg) -> True) (reverse logs) of Just (EndRun (Just d) _ ) -> d Nothing -> case queryClosingDate dt of - Right d' -> d' - Left err -> error $ "Error in BalanceFormula: " ++ err + Right d' -> d' + Left err -> error $ "Error in BalanceFormula: " ++ err v = case queryDealType dt _date (Q.patchDateToStats _date ds) of Right v' -> fromRational v' Left err -> error $ "Error in BalanceFormula: " ++ err diff --git a/src/Analytics.hs b/src/Analytics.hs index cbe63377..57c1c60d 100644 --- a/src/Analytics.hs +++ b/src/Analytics.hs @@ -17,7 +17,7 @@ import Language.Haskell.TH import Data.Aeson.TH import Data.Aeson.Types import Data.Ord (comparing) -import Data.List (sortBy) +import Data.List (sortBy, uncons) import GHC.Generics import Data.Ratio import Numeric.RootFinding @@ -61,8 +61,8 @@ initialSlopes points@((t0, y0):rest) = let deltas = secantSlopes points n = length points slopes = [head deltas] ++ -- First slope: use first secant slope - [ (d1 + d2) / 2 | (d1, d2) <- zip deltas (tail deltas) ] ++ -- Interior slopes - [last deltas] -- Last slope: use last secant slope + [ (d1 + d2) / 2 | (d1, d2) <- zip deltas (tail deltas) ] ++ -- Interior slopes + [last deltas] -- Last slope: use last secant slope in slopes fritschCarlsonSlopes :: [TimeYield] -> [Double] @@ -239,9 +239,9 @@ fv2 discount_rate today futureDay amt calcPvFromIRR :: Double -> [Date] -> [Amount] -> Date -> Double -> Double calcPvFromIRR irr [] _ d amt = 0 -calcPvFromIRR irr ds vs d amt = +calcPvFromIRR irr ds@(sd:_) vs d amt = let - begDate = head ds + begDate = sd vs' = fromRational . toRational <$> vs pv = pv22 irr begDate (ds++[d]) (vs'++[amt]) in @@ -260,9 +260,8 @@ calcRequiredAmtForIrrAtDate irr ds vs d = error -> Nothing -- `debug` ("calcRequiredAmtForIrrAtDate: error"++ show error) -- ^ calc IRR from a cashflow -calcIRR :: [Date] -> [Amount] -> Either String Rate +calcIRR :: [Date] -> [Amount] -> Either ErrorRep Rate calcIRR _ [] = Left "No cashflow amount" -calcIRR [] _ = Left "No cashflow date" calcIRR ds vs | all (>= 0) vs = Left $ "All cashflow can't be all positive:"++ show vs | all (<= 0) vs = return $ -1.0 @@ -271,11 +270,15 @@ calcIRR ds vs let itertimes = 1000 def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.000001} - beginDate = head ds vs' = fromRational . toRational <$> vs - sumOfPv irr = pv22 irr beginDate ds vs' - in - case ridders def (-1,1000) sumOfPv of - Root irrRate -> return $ toRational irrRate - NotBracketed -> Left $ "IRR: not bracketed" ++ show vs' ++ " and dates"++ show ds - SearchFailed -> Left $ "IRR: search failed: can't be calculated with input "++ show vs++" and dates"++ show ds + in + case uncons ds of + Nothing -> Left "calcIRR: empty dates" + Just (beginDate, _) -> + let + sumOfPv irr = pv22 irr beginDate ds vs' + in + case ridders def (-1,1000) sumOfPv of + Root irrRate -> return $ toRational irrRate + NotBracketed -> Left $ "IRR: not bracketed" ++ show vs' ++ " and dates"++ show ds + SearchFailed -> Left $ "IRR: search failed: can't be calculated with input "++ show vs++" and dates"++ show ds diff --git a/src/Asset.hs b/src/Asset.hs index ee3783c8..86309429 100644 --- a/src/Asset.hs +++ b/src/Asset.hs @@ -187,13 +187,13 @@ buildPrepayRates a ds (Just (A.PrepaymentCPR r)) buildPrepayRates a ds (Just (A.PrepaymentVec vs)) | any (> 1.0) vs || any (< 0.0) vs = Left $ "buildPrepayRates: prepayment vector should be between 0 and 1, got " ++ show vs | otherwise = return $ zipWith Util.toPeriodRateByInterval - (paddingDefault 0.0 vs (pred (length ds))) - (getIntervalDays ds) + (paddingDefault 0.0 vs (pred (length ds))) + (getIntervalDays ds) buildPrepayRates a ds (Just (A.PrepaymentVecPadding vs)) | any (> 1.0) vs || any (< 0.0) vs = Left $ "buildPrepayRates: prepayment vector should be between 0 and 1, got " ++ show vs | otherwise = return $ zipWith Util.toPeriodRateByInterval - (paddingDefault (last vs) vs (pred (length ds))) - (getIntervalDays ds) + (paddingDefault (last vs) vs (pred (length ds))) + (getIntervalDays ds) buildPrepayRates a ds (Just (A.PrepayStressByTs ts x)) | any (< 0.0) (getTsVals ts) = Left $ "buildPrepayRates: prepayment vector by ts should be non-negative, got " ++ show (getTsVals ts) | otherwise = do @@ -233,13 +233,13 @@ buildDefaultRates a ds (Just (A.DefaultCDR r)) buildDefaultRates a ds (Just (A.DefaultVec vs)) | any (> 1.0) vs || any (< 0.0) vs = Left $ "buildDefaultRates: default vector should be between 0 and 1, got " ++ show vs | otherwise = return $ zipWith Util.toPeriodRateByInterval - (paddingDefault 0.0 vs (pred (length ds))) - (getIntervalDays ds) + (paddingDefault 0.0 vs (pred (length ds))) + (getIntervalDays ds) buildDefaultRates a ds (Just (A.DefaultVecPadding vs)) | any (> 1.0) vs || any (< 0.0) vs = Left $ "buildDefaultRates: default vector should be between 0 and 1, got " ++ show vs | otherwise = return $ zipWith Util.toPeriodRateByInterval - (paddingDefault (last vs) vs (pred (length ds))) - (getIntervalDays ds) + (paddingDefault (last vs) vs (pred (length ds))) + (getIntervalDays ds) buildDefaultRates a ds (Just (A.DefaultAtEndByRate r rAtEnd)) | r > 1.0 || r < 0.0 = Left $ "buildDefaultRates: default at end rate should be between 0 and 1, got " ++ show r | rAtEnd > 1.0 || rAtEnd < 0.0 = Left $ "buildDefaultRates: default at end rate should be between 0 and 1, got " ++ show rAtEnd diff --git a/src/Assumptions.hs b/src/Assumptions.hs index d12583aa..c46fa122 100644 --- a/src/Assumptions.hs +++ b/src/Assumptions.hs @@ -156,7 +156,7 @@ data AssumptionInput = Single ApplyAssumptionType NonPerfAssumption data AssetDefaultAssumption = DefaultConstant Rate -- ^ using constant default rate | DefaultCDR Rate -- ^ using annualized default rate | DefaultVec [Rate] -- ^ using default rate vector - | DefaultVecPadding [Rate] -- ^ using default rate vector, but padding with last rate till end + | DefaultVecPadding [Rate] -- ^ using default rate vector, but padding with Last rate till end | DefaultByAmt (Balance,[Rate]) | DefaultAtEnd -- ^ default 100% at end | DefaultAtEndByRate Rate Rate -- ^ life time default rate and default rate at end diff --git a/src/Cashflow.hs b/src/Cashflow.hs index 2980d8e0..1473fba2 100644 --- a/src/Cashflow.hs +++ b/src/Cashflow.hs @@ -728,13 +728,12 @@ emptyTsRow _d (BondFlow a x c d) = BondFlow _d 0 0 0 emptyTsRow _d (ReceivableFlow a x c d e f g h i) = ReceivableFlow _d 0 0 0 0 0 0 0 Nothing extendCashFlow :: Date -> CashFlowFrame -> CashFlowFrame -extendCashFlow d (CashFlowFrame st []) = CashFlowFrame st [] -extendCashFlow d (CashFlowFrame st txns) - = let - lastRow = last txns - newTxn = emptyTsRow d lastRow - in - CashFlowFrame st (txns++[newTxn]) +extendCashFlow d (CashFlowFrame st txns) + = case unsnoc txns of + Nothing -> CashFlowFrame st [] + Just (initTxns, lastRow) -> + let newTxn = emptyTsRow d lastRow + in CashFlowFrame st (txns ++ [newTxn]) viewTsRow :: Date -> TsRow -> TsRow diff --git a/src/Deal.hs b/src/Deal.hs index 29cf542e..8f888f67 100644 --- a/src/Deal.hs +++ b/src/Deal.hs @@ -98,33 +98,6 @@ updateSrtRate t d ras srt@HE.SRT{HE.srtPremiumType = rt} return srt { HE.srtPremiumRate = r } -accrueSrt :: Ast.Asset a => TestDeal a -> Date -> HE.SRT -> Either String HE.SRT -accrueSrt t d srt@HE.SRT{ HE.srtDuePremium = duePrem, HE.srtRefBalance = bal, HE.srtPremiumRate = rate - , HE.srtDuePremiumDate = mDueDate, HE.srtType = st - , HE.srtStart = sd } - = do - newBal <- case st of - HE.SrtByEndDay ds dp -> queryCompound t d (patchDateToStats d ds) - let newPremium = duePrem + calcInt (fromRational newBal) (fromMaybe sd mDueDate) d rate DC_ACT_365F - let accrueInt = calcInt (HE.srtRefBalance srt + duePrem) (fromMaybe d (HE.srtDuePremiumDate srt)) d (HE.srtPremiumRate srt) DC_ACT_365F - return srt { HE.srtRefBalance = fromRational newBal, HE.srtDuePremium = newPremium, HE.srtDuePremiumDate = Just d} - - --- ^ test if a clean up call should be fired -testCall :: Ast.Asset a => TestDeal a -> Date -> C.CallOption -> Either ErrorRep Bool -testCall t d opt = - case opt of - C.PoolBalance x -> (< x) . fromRational <$> queryCompound t d (FutureCurrentPoolBalance Nothing) - C.BondBalance x -> (< x) . fromRational <$> queryCompound t d CurrentBondBalance - C.PoolFactor x -> (< x) <$> queryCompound t d (FutureCurrentPoolFactor d Nothing) - C.BondFactor x -> (< x) <$> queryCompound t d BondFactor - C.OnDate x -> return $ x == d - C.AfterDate x -> return $ d > x - C.And xs -> allM (testCall t d) xs - C.Or xs -> anyM (testCall t d) xs - C.Pre pre -> testPre d t pre - _ -> Left ("failed to find call options"++ show opt) - queryTrigger :: Ast.Asset a => TestDeal a -> DealCycle -> [Trigger] queryTrigger t@TestDeal{ triggers = trgs } wt @@ -265,10 +238,10 @@ priceBonds t@TestDeal {bonds = bndMap} (AP.IrrInput bMapInput) -- , runDeal :: Ast.Asset a => TestDeal a -> S.Set ExpectReturn -> Maybe AP.ApplyAssumptionType-> AP.NonPerfAssumption -> Either String (TestDeal a - , Map.Map PoolId CF.CashFlowFrame - , [ResultComponent] - , Map.Map String PriceResult - , Map.Map PoolId CF.PoolCashflow) + , Map.Map PoolId CF.CashFlowFrame + , [ResultComponent] + , Map.Map String PriceResult + , Map.Map PoolId CF.PoolCashflow) runDeal t er perfAssumps nonPerfAssumps@AP.NonPerfAssumption{AP.callWhen = opts ,AP.pricing = mPricing ,AP.revolving = mRevolving ,AP.interest = mInterest} | not runFlag = Left $ intercalate ";" $ show <$> valLogs | otherwise @@ -293,11 +266,11 @@ runDeal t er perfAssumps nonPerfAssumps@AP.NonPerfAssumption{AP.callWhen = opts (Just p) -> priceBonds finalDeal p Nothing -> Right Map.empty return (finalDeal - , poolFlowUsedNoEmpty - , getRunResult finalDeal ++ V.validateRun finalDeal ++ DL.toList (DL.append logs (unCollectedPoolFlowWarning poolFlowUnUsed)) - , bndPricing - , poolFlowUnUsed - ) + , poolFlowUsedNoEmpty + , getRunResult finalDeal ++ V.validateRun finalDeal ++ DL.toList (DL.append logs (unCollectedPoolFlowWarning poolFlowUnUsed)) + , bndPricing + , poolFlowUnUsed + ) where (runFlag, valLogs) = V.validateReq t nonPerfAssumps -- getinits() will get (new deal snapshot, actions, pool cashflows, unstressed pool cashflow) diff --git a/src/Deal/DealBase.hs b/src/Deal/DealBase.hs index e485915a..cd4635bc 100644 --- a/src/Deal/DealBase.hs +++ b/src/Deal/DealBase.hs @@ -103,7 +103,6 @@ data ActionOnDate = EarnAccInt Date AccName -- ^ sweep bank account | StepUpBondRate Date String -- ^ reset bond interest rate per bond's interest rate info | ResetSrtRate Date String | ResetAccRate Date String - | AccrueSrt Date String | MakeWhole Date Spread (Table Float Spread) | IssueBond Date (Maybe Pre) String AccName L.Bond (Maybe DealStats) (Maybe DealStats) | FundBond Date (Maybe Pre) String AccName Amount @@ -630,8 +629,8 @@ getAllAsset :: TestDeal a -> Maybe [PoolId] -> Map.Map PoolId [a] getAllAsset t@TestDeal{pool = pt} mPns = let assetMap = case pt of - MultiPool pm -> Map.map P.assets pm - ResecDeal _ -> Map.empty + MultiPool pm -> Map.map P.assets pm + ResecDeal _ -> Map.empty -- ResecDeal pm -> Map.mapWithKey (\(UnderlyingBond (bn,hpct,sd), d) -> getAllAsset d Nothing) pm in case mPns of @@ -655,7 +654,7 @@ getAllCollectedFrame t@TestDeal{pool = poolType} mPid = getLatestCollectFrame :: Ast.Asset a => TestDeal a -> Maybe [PoolId] -> Map.Map PoolId (Maybe CF.TsRow) getLatestCollectFrame t mPns = Map.map (\case (CF.CashFlowFrame (_,_,_) []) -> Nothing - (CF.CashFlowFrame (_,_,_) txns) -> Just $ last txns + (CF.CashFlowFrame (_,_,_) txns) -> snd <$> Data.List.unsnoc txns ) (getAllCollectedFrame t mPns) diff --git a/src/Liability.hs b/src/Liability.hs index 1cd555e3..2980e78d 100644 --- a/src/Liability.hs +++ b/src/Liability.hs @@ -320,60 +320,6 @@ patchBondFactor bnd in newBnd --- payInt :: Date -> Amount -> Bond -> Bond --- pay 0 interest, do nothing --- payInt d 0 b = b --- --- -- pay interest --- payInt d amt bnd@(Bond bn bt oi iinfo _ bal r duePrin dueInt dueIoI dueIntDate lpayInt lpayPrin stmt) --- = bnd {bndDueInt=newDue, bndStmt=newStmt, bndLastIntPay = Just d, bndDueIntOverInt = newDueIoI} --- where --- rs = Lib.paySeqLiabilitiesAmt amt [dueIoI, dueInt] -- `debug` ("date"++ show d++"due "++show dueIoI++">>"++show dueInt) --- newDueIoI = dueIoI - head rs --- newDue = dueInt - rs !! 1 -- `debug` ("Avail fund"++ show amt ++" int paid out plan"++ show rs) --- newStmt = case bt of --- Equity -> S.appendStmt (BondTxn d bal amt 0 r amt newDue newDueIoI Nothing (S.PayYield bn)) stmt --- _ -> S.appendStmt (BondTxn d bal amt 0 r amt newDue newDueIoI Nothing (S.PayInt [bn])) stmt -- `debug` ("date after"++ show d++"due "++show newDueIoI++">>"++show newDue) --- --- -- pay multi-int bond ,IOI first and then interest due, sequentially --- payInt d amt bnd@(MultiIntBond bn bt oi iinfo _ bal rs duePrin dueInts dueIoIs dueIntDate lpayInt lpayPrin stmt) --- = bnd {bndDueInts=newDues, bndStmt=newStmt , bndLastIntPays = Just (replicate l d), bndDueIntOverInts = newDueIoIs} --- where --- l = length iinfo --- ioiPaid = Lib.paySeqLiabilitiesAmt amt dueIoIs --- afterIoI = amt - sum ioiPaid --- duePaid = Lib.paySeqLiabilitiesAmt afterIoI dueInts --- newDueIoIs = zipWith (-) dueIoIs ioiPaid --- newDues = zipWith (-) dueInts duePaid --- newDueIoI = sum newDueIoIs --- newDue = sum newDues --- newStmt = S.appendStmt (BondTxn d bal amt 0 (sum rs) amt newDue newDueIoI Nothing (S.PayInt [bn])) stmt --- --- payIntByIndex :: Date -> Int -> Amount -> Bond -> Bond --- -- pay 0 interest, do nothing --- payIntByIndex d _ 0 b = b --- payIntByIndex d idx amt bnd@(MultiIntBond bn bt oi iinfo _ bal rs duePrin dueInts dueIoIs dueIntDate lpayInt lpayPrin stmt) --- = let --- dueIoI = dueIoIs !! idx --- dueInt = dueInts !! idx -- `debug` ("date"++ show d++"in pay index fun"++ show amt) --- [newDueIoI,newDue] = Lib.paySeqLiabResi amt [dueIoI, dueInt] -- `debug` ("date"++ show d++" before pay due "++show dueIoI++">>"++show dueInt) --- newStmt = S.appendStmt (BondTxn d bal amt 0 (sum rs) amt newDue newDueIoI Nothing (S.PayInt [bn])) stmt -- `debug` ("date after"++ show d++"due(ioi) "++show newDueIoI++">> due "++show newDue) --- od = getOriginDate bnd --- ods = replicate (length iinfo) od --- in --- bnd {bndDueInts = dueInts & ix idx .~ newDue --- ,bndDueIntOverInts = dueIoIs & ix idx .~ newDueIoI --- ,bndStmt = newStmt --- ,bndLastIntPays = case lpayInt of --- Nothing -> Just $ ods & ix idx .~ d --- Just ds -> Just $ ds & ix idx .~ d} - - --- ^ pay interest to single bond regardless any interest due, for equity tranche - --- AllInterest = DueTotalOf [DueInterest Nothing, DueArrears] - - instance Payable Bond where diff --git a/src/Reports.hs b/src/Reports.hs index 792fc9be..972de836 100644 --- a/src/Reports.hs +++ b/src/Reports.hs @@ -21,7 +21,7 @@ import qualified Liability as L import Control.Applicative (liftA3) import Types import Deal.DealBase - ( TestDeal(TestDeal, pool, fees, bonds, accounts,liqProvider,rateSwap), getIssuanceStatsConsol, getAllCollectedFrame ,poolTypePool, dealPool) + ( TestDeal(TestDeal, pool, fees, bonds, accounts,liqProvider,rateSwap), getIssuanceStatsConsol ,poolTypePool, dealPool) import Deal.DealQuery ( queryCompound ) import Deal.DealAction ( calcDueFee, calcDueInt ) import Data.Maybe (fromMaybe) diff --git a/swagger.json b/swagger.json index c754705e..1127aa4a 100644 --- a/swagger.json +++ b/swagger.json @@ -5792,6 +5792,28 @@ "title": "CumulativeNetLoss", "type": "object" }, + { + "properties": { + "contents": { + "items": { + "$ref": "#/components/schemas/PoolId" + }, + "type": "array" + }, + "tag": { + "enum": [ + "PoolAccruedInterest" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "PoolAccruedInterest", + "type": "object" + }, { "properties": { "tag": { @@ -6415,6 +6437,38 @@ "title": "PoolScheduleCfPv", "type": "object" }, + { + "properties": { + "contents": { + "items": [ + { + "$ref": "#/components/schemas/Day" + }, + { + "items": { + "$ref": "#/components/schemas/PoolId" + }, + "type": "array" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "tag": { + "enum": [ + "FuturePoolAccruedInterest" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "FuturePoolAccruedInterest", + "type": "object" + }, { "properties": { "contents": { @@ -8097,6 +8151,28 @@ "title": "IsOutstanding", "type": "object" }, + { + "properties": { + "contents": { + "items": { + "type": "string" + }, + "type": "array" + }, + "tag": { + "enum": [ + "IsAnyOutstanding" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "IsAnyOutstanding", + "type": "object" + }, { "properties": { "contents": { From 74b04ee9216d44efeced06e7a6afa60c7448be93 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Tue, 3 Mar 2026 14:56:13 +0800 Subject: [PATCH 11/17] console deal runContext --- src/Deal.hs | 4 +- src/Deal/DealAction.hs | 6 +- src/Deal/DealRun.hs | 206 ++++++++++++++++++++--------------------- 3 files changed, 104 insertions(+), 112 deletions(-) diff --git a/src/Deal.hs b/src/Deal.hs index 8f888f67..a37a98ec 100644 --- a/src/Deal.hs +++ b/src/Deal.hs @@ -248,11 +248,9 @@ runDeal t er perfAssumps nonPerfAssumps@AP.NonPerfAssumption{AP.callWhen = opts = do (newT, ads, pcf, unStressPcf) <- getInits er t perfAssumps (Just nonPerfAssumps) (_finalDeal, logs, osPoolFlow) <- run newT - pcf + (RunContext pcf mRevolvingCtx mInterest) (Just ads) - mInterest (AP.readCallOptions <$> opts) - mRevolvingCtx DL.empty -- prepare deal with expected return let finalDeal = prepareDeal er _finalDeal diff --git a/src/Deal/DealAction.hs b/src/Deal/DealAction.hs index 5fe816e9..165f259d 100644 --- a/src/Deal/DealAction.hs +++ b/src/Deal/DealAction.hs @@ -412,7 +412,7 @@ buyRevolvingPool d r rp@(AssetCurve aus) (assetBought, rp) -data RunContext a = RunContext{ +data RunContext = RunContext{ runPoolFlow:: Map.Map PoolId CF.PoolCashflow ,revolvingAssump:: Maybe (Map.Map String (RevolvingPool ,AP.ApplyAssumptionType)) ,revolvingInterestRateAssump:: Maybe [RateAssumption] @@ -600,8 +600,8 @@ updateSupport d (Just support) bal t = (deal,amt) <- drawExtraSupport d bal support t return deal -performActionWrap :: Ast.Asset a => Date -> (TestDeal a, RunContext a, DL.DList ResultComponent) - -> W.Action -> Either ErrorRep (TestDeal a, RunContext a, DL.DList ResultComponent) +performActionWrap :: Ast.Asset a => Date -> (TestDeal a, RunContext, DL.DList ResultComponent) + -> W.Action -> Either ErrorRep (TestDeal a, RunContext, DL.DList ResultComponent) performActionWrap d (t, rc, logs) (W.BuyAsset ml pricingMethod accName pId) diff --git a/src/Deal/DealRun.hs b/src/Deal/DealRun.hs index 55f9a2e6..bfb7acfa 100644 --- a/src/Deal/DealRun.hs +++ b/src/Deal/DealRun.hs @@ -45,8 +45,8 @@ debug = flip trace -- ^ execute effects of trigger: making changes to deal -- TODO seems position of arugments can be changed : f :: a -> b -> m a => f:: b -> a -> m a -runEffects :: Ast.Asset a => (TestDeal a, RunContext a, [ActionOnDate], DL.DList ResultComponent) -> Date -> TriggerEffect - -> Either String (TestDeal a, RunContext a, [ActionOnDate], DL.DList ResultComponent) +runEffects :: Ast.Asset a => (TestDeal a, RunContext, [ActionOnDate], DL.DList ResultComponent) -> Date -> TriggerEffect + -> Either String (TestDeal a, RunContext, [ActionOnDate], DL.DList ResultComponent) runEffects (t@TestDeal{accounts = accMap, fees = feeMap ,status=st, bonds = bondMap, pool=pt ,collects = collRules}, rc, actions, logs) d te = case te of @@ -164,29 +164,29 @@ updateRateSwapRate t (Just rAssumps) d rs@HE.RateSwap{ HE.rsType = rt } in do (pRate,rRate) <- case rt of - HE.FloatingToFloating flter1 flter2 -> - do - r1 <- getRate flter1 - r2 <- getRate flter2 - return (r1, r2) - HE.FloatingToFixed flter r -> - do - _r <- getRate flter - return (_r, r) - HE.FixedToFloating r flter -> - do - _r <- getRate flter - return (r, _r) - HE.FormulaToFloating ds flter -> - do - _r <- queryCompound t d (patchDateToStats d ds) - r <- getRate flter - return (fromRational _r, r) - HE.FloatingToFormula flter ds -> - do - r <- getRate flter - _r <- queryCompound t d (patchDateToStats d ds) - return (r, fromRational _r) + HE.FloatingToFloating flter1 flter2 -> + do + r1 <- getRate flter1 + r2 <- getRate flter2 + return (r1, r2) + HE.FloatingToFixed flter r -> + do + _r <- getRate flter + return (_r, r) + HE.FixedToFloating r flter -> + do + _r <- getRate flter + return (r, _r) + HE.FormulaToFloating ds flter -> + do + _r <- queryCompound t d (patchDateToStats d ds) + r <- getRate flter + return (fromRational _r, r) + HE.FloatingToFormula flter ds -> + do + r <- getRate flter + _r <- queryCompound t d (patchDateToStats d ds) + return (r, fromRational _r) return rs {HE.rsPayingRate = pRate, HE.rsReceivingRate = rRate } updateLiqProviderRate :: Ast.Asset a => TestDeal a -> Date -> [RateAssumption] -> CE.LiqFacility -> CE.LiqFacility @@ -199,7 +199,7 @@ updateLiqProviderRate t d ras liq@CE.LiqFacility{CE.liqRateType = mRt, CE.liqPre in liq {CE.liqRate = newMr, CE.liqPremiumRate = newMpr } -runTriggers :: Ast.Asset a => (TestDeal a, RunContext a, [ActionOnDate]) -> Date -> DealCycle -> Either String (TestDeal a, RunContext a, [ActionOnDate], DL.DList ResultComponent) +runTriggers :: Ast.Asset a => (TestDeal a, RunContext, [ActionOnDate]) -> Date -> DealCycle -> Either String (TestDeal a, RunContext, [ActionOnDate], DL.DList ResultComponent) runTriggers (t@TestDeal{status=oldStatus, triggers = Nothing},rc, actions) d dcycle = return (t, rc, actions, DL.empty) runTriggers (t@TestDeal{status=oldStatus, triggers = Just trgM},rc, actions) d dcycle = do @@ -277,25 +277,33 @@ appendCollectedCF d t@TestDeal { pool = ResecDeal uds } poolInflowMap t {pool = newPt} -run :: Ast.Asset a => TestDeal a -> Map.Map PoolId CF.PoolCashflow -> Maybe [ActionOnDate] -> Maybe [RateAssumption] -> Maybe ([Pre],[Pre]) - -> Maybe (Map.Map String (RevolvingPool,AP.ApplyAssumptionType)) -> DL.DList ResultComponent - -> Either String (TestDeal a, DL.DList ResultComponent, Map.Map PoolId CF.PoolCashflow) -run t@TestDeal{status=(Ended endedDate)} pCfM ads _ _ _ log = return (t,DL.snoc log (EndRun endedDate "By Status:Ended"), pCfM) -run t pCfM (Just []) _ _ _ log = return (t,DL.snoc log (EndRun Nothing "No Actions"), pCfM) -run t pCfM (Just [HitStatedMaturity d]) _ _ _ log = return (t, DL.snoc log (EndRun (Just d) "Stop: Stated Maturity"), pCfM) -run t pCfM (Just (StopRunFlag d:_)) _ _ _ log = return (t, DL.snoc log (EndRun (Just d) "Stop Run Flag"), pCfM) +run :: Ast.Asset a => TestDeal a + -> RunContext + -> Maybe [ActionOnDate] + -> Maybe ([Pre],[Pre]) + -> DL.DList ResultComponent + -> Either ErrorRep (TestDeal a, DL.DList ResultComponent, Map.Map PoolId CF.PoolCashflow) +-- ^ Ended by Status +run t@TestDeal{status=(Ended endedDate)} (RunContext pCfM _ _) ads _ log = return (t,DL.snoc log (EndRun endedDate "By Status:Ended"), pCfM) +-- ^ Ended by No more Actions +run t (RunContext pCfM _ _) (Just []) _ log = return (t,DL.snoc log (EndRun Nothing "No Actions"), pCfM) +-- ^ Ended by Stated Maturity +run t (RunContext pCfM _ _) (Just [HitStatedMaturity d]) _ log = return (t, DL.snoc log (EndRun (Just d) "Stop: Stated Maturity"), pCfM) +-- ^ Ended by Stop Run Flag +run t (RunContext pCfM _ _) (Just (StopRunFlag d:_)) _ log = return (t, DL.snoc log (EndRun (Just d) "Stop Run Flag"), pCfM) + run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status=dStatus ,waterfall=waterfallM,name=dealName,pool=pt,stats=_stat} - poolFlowMap (Just (ad:ads)) rates calls rAssump log + rc@(RunContext poolFlowMap rAssump rates) + (Just (ad:ads)) calls log | futureCashToCollectFlag && (queryCompound t (getDate ad) AllAccBalance == Right 0) && (dStatus /= Revolving) && (dStatus /= Warehousing Nothing) --TODO need to use prsim here to cover all warehouse status = let - runContext = RunContext poolFlowMap rAssump rates --- `debug` ("ending at date " ++ show (getDate ad)) endingLog = EndRun (Just (getDate ad)) "No Pool Cashflow/All Account is zero/Not revolving" endingDate = getDate ad in if Map.member W.CleanUp waterfallM then do - (finalDeal,RunContext newPoolFlowMap _ _,newLogs) <- foldM (performActionWrap endingDate) (t,runContext,log) cleanUpActions + (finalDeal,RunContext newPoolFlowMap _ _,newLogs) <- foldM (performActionWrap endingDate) (t,rc,log) cleanUpActions return (finalDeal, DL.concat [newLogs, DL.fromList [RunningWaterfall endingDate W.CleanUp, endingLog] ] , newPoolFlowMap) else return (t , DL.snoc log endingLog, poolFlowMap) @@ -331,28 +339,25 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= (dAfterAction,rc2,newLogs) <- foldM (performActionWrap d) (dRunWithTrigger0 ,rc1 ,log ) waterfallToExe (dRunWithTrigger1, rc3, ads3, newLogs1) <- runTriggers (dAfterAction, rc2, ads2) d EndCollectionWF run (increasePoolCollectedPeriod dRunWithTrigger1 ) - (runPoolFlow rc3) + rc3 (Just ads3) - rates calls - rAssump (DL.concat [newLogs0,newLogs,eopActionsLog,newLogs1]) else - run t poolFlowMap (Just ads) rates calls rAssump log + run t rc (Just ads) calls log AccruePoolCollection d x -> do t' <- (accrueDeal d (fromMaybe [] rates) t) - run t' poolFlowMap (Just (PoolCollection d x:ads)) rates calls rAssump log + run t' rc (Just (PoolCollection d x:ads)) calls log AccrueRunWaterfall d x -> do t' <- (accrueDeal d (fromMaybe [] rates) t) - run t' poolFlowMap (Just (RunWaterfall d x:ads)) rates calls rAssump log + run t' rc (Just (RunWaterfall d x:ads)) calls log RunWaterfall d "" -> let - runContext = RunContext poolFlowMap rAssump rates waterfallKey | Map.member (W.DistributionDay dStatus) waterfallM = W.DistributionDay dStatus | otherwise = W.DefaultDistribution @@ -362,7 +367,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= in do -- Run triggers before waterfall distribution - (dRunWithTrigger0, rc1, ads1, newLogs0) <- runTriggers (t, runContext, ads) d BeginDistributionWF + (dRunWithTrigger0, rc1, ads1, newLogs0) <- runTriggers (t, rc, ads) d BeginDistributionWF let logsBeforeDist | Map.notMember waterfallKey waterfallM = DL.snoc newLogs0 @@ -387,62 +392,58 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= (dAfterWaterfall, rc2, newLogsWaterfall) <- foldM (performActionWrap d) (dRunWithTrigger0,rc1,log) waterfallToExe (dRunWithTrigger1, rc3, ads2, newLogs2) <- runTriggers (dAfterWaterfall,rc2,ads1) d EndDistributionWF run (increaseBondPaidPeriod dRunWithTrigger1) - (runPoolFlow rc3) + rc3 (Just ads2) - rates calls - rAssump (DL.concat [newLogsWaterfall, newLogs2 ,logsBeforeDist,DL.fromList [RunningWaterfall d waterfallKey]]) -- Custom waterfall execution action from custom dates RunWaterfall d wName -> let - runContext = RunContext poolFlowMap rAssump rates waterfallKey = W.CustomWaterfall wName in do waterfallToExe <- lookupM waterfallKey waterfallM let logsBeforeDist =[ WarningMsg (" No waterfall distribution found on date "++show d++" with waterfall key "++show waterfallKey) | Map.notMember waterfallKey waterfallM ] - (dAfterWaterfall, rc2, newLogsWaterfall) <- foldM (performActionWrap d) (t,runContext,log) waterfallToExe - run dAfterWaterfall (runPoolFlow rc2) (Just ads) rates calls rAssump + (dAfterWaterfall, rc2, newLogsWaterfall) <- foldM (performActionWrap d) (t,rc,log) waterfallToExe + run dAfterWaterfall rc2 (Just ads) calls (DL.concat [newLogsWaterfall,DL.fromList (logsBeforeDist ++ [RunningWaterfall d waterfallKey])]) EarnAccInt d accName -> let newAcc = Map.adjust (A.depositInt d) accName accMap in - run (t {accounts = newAcc}) poolFlowMap (Just ads) rates calls rAssump log + run (t {accounts = newAcc}) rc (Just ads) calls log AccrueFee d feeName -> do fToAcc <- maybeToEither ("Failed to find fee "++feeName) (Map.lookup feeName feeMap) newF <- calcDueFee t d fToAcc let newFeeMap = Map.fromList [(feeName,newF)] <> feeMap - run (t{fees=newFeeMap}) poolFlowMap (Just ads) rates calls rAssump log + run (t{fees=newFeeMap}) rc (Just ads) calls log ResetLiqProvider d liqName -> case liqProvider t of - Nothing -> run t poolFlowMap (Just ads) rates calls rAssump log + Nothing -> run t rc (Just ads) calls log (Just mLiqProvider) -> let -- update credit newLiqMap = Map.adjust (updateLiqProvider t d) liqName mLiqProvider in - run (t{liqProvider = Just newLiqMap}) poolFlowMap (Just ads) rates calls rAssump log - + run (t{liqProvider = Just newLiqMap}) rc (Just ads) calls log ResetLiqProviderRate d liqName -> case liqProvider t of - Nothing -> run t poolFlowMap (Just ads) rates calls rAssump log + Nothing -> run t rc (Just ads) calls log (Just mLiqProvider) -> let -- update rate newLiqMap = Map.adjust (updateLiqProviderRate t d (fromMaybe [] rates)) liqName mLiqProvider in - run (t{liqProvider = Just newLiqMap}) poolFlowMap (Just ads) rates calls rAssump log + run (t{liqProvider = Just newLiqMap}) rc (Just ads) calls log DealClosed d -> let w = Map.findWithDefault [] W.OnClosingDay (waterfall t) - rc = RunContext poolFlowMap rAssump rates + -- rc = RunContext poolFlowMap rAssump rates logForClosed = [RunningWaterfall d W.OnClosingDay| not (null w)] in do @@ -450,10 +451,10 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= (PreClosing st) -> return st _ -> Left $ "DealClosed action is not in PreClosing status but got"++ show dStatus (newDeal, newRc, newLog) <- foldM (performActionWrap d) (t, rc, log) w -- `debug` ("ClosingDay Action:"++show w) - run newDeal{status=newSt} (runPoolFlow newRc) (Just ads) rates calls rAssump + run newDeal{status=newSt} newRc (Just ads) calls (DL.concat [newLog, DL.fromList ([DealStatusChangeTo d (PreClosing newSt) newSt "By Deal Close"]++logForClosed)]) -- `debug` ("new st at closing"++ show newSt) - ChangeDealStatusTo d s -> run (t{status=s}) poolFlowMap (Just ads) rates calls rAssump log + ChangeDealStatusTo d s -> run (t{status=s}) rc (Just ads) calls log CalcIRSwap d sn -> case rateSwap t of @@ -463,7 +464,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newRateSwap_rate <- adjustM (updateRateSwapRate t rates d) sn rSwap newRateSwap_bal <- adjustM (updateRateSwapBal t d) sn newRateSwap_rate let newRateSwap_acc = Map.adjust (HE.accrueIRS d) sn newRateSwap_bal - run (t{rateSwap = Just newRateSwap_acc}) poolFlowMap (Just ads) rates calls rAssump log + run (t{rateSwap = Just newRateSwap_acc}) rc (Just ads) calls log SettleIRSwap d sn -> case rateSwap t of @@ -483,23 +484,19 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newAcc <- adjustM (A.draw d accBal (SwapOutSettle sn)) accName accMap let newRsMap = Just $ Map.adjust (HE.payoutIRS d accBal) sn rSwap run (t {accounts = newAcc, rateSwap = newRsMap}) - poolFlowMap (Just ads) - rates - calls - rAssump - (DL.snoc log (WarningMsg ("Settle Rate Swap Error: "++ show d ++" Insufficient balance to settle "++ sn))) + rc (Just ads) calls log -- Left $ "Settle Rate Swap Error: "++ show d ++" Insufficient balance to settle "++ sn (True, False) -> do newAcc <- adjustM (A.draw d (abs settleAmt) (SwapOutSettle sn)) accName accMap let newRsMap = Just $ Map.adjust (HE.payoutIRS d settleAmt) sn rSwap - run (t{accounts = newAcc, rateSwap = newRsMap}) poolFlowMap (Just ads) rates calls rAssump log + run (t{accounts = newAcc, rateSwap = newRsMap}) rc (Just ads) calls log (False, _) -> let newAcc = Map.adjust (A.deposit settleAmt d (SwapInSettle sn)) accName accMap newRsMap = Just $ Map.adjust (HE.receiveIRS d) sn rSwap in - run (t{accounts = newAcc, rateSwap = newRsMap}) poolFlowMap (Just ads) rates calls rAssump log + run (t{accounts = newAcc, rateSwap = newRsMap}) rc (Just ads) calls log AccrueCapRate d cn -> case rateCap t of @@ -510,12 +507,12 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= in do newRateCap <- adjustM (accrueRC t d _rates) cn rCap - run (t{rateCap = Just newRateCap}) poolFlowMap (Just ads) rates calls rAssump log + run (t{rateCap = Just newRateCap}) rc (Just ads) calls log InspectDS d dss -> do newlog <- inspectListVars t d dss - run t poolFlowMap (Just ads) rates calls rAssump $ DL.append log (DL.fromList newlog) + run t rc (Just ads) calls log ResetBondRate d bn -> let @@ -524,7 +521,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= in do newBnd <- setBondNewRate t d rateList bnd - run t{bonds = Map.fromList [(bn,newBnd)] <> bndMap} poolFlowMap (Just ads) rates calls rAssump log + run t{bonds = Map.fromList [(bn,newBnd)] <> bndMap} rc (Just ads) calls log StepUpBondRate d bn -> let @@ -532,7 +529,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= in do newBndMap <- adjustM (setBondStepUpRate d (fromMaybe [] rates)) bn bndMap - run t{bonds = newBndMap } poolFlowMap (Just ads) rates calls rAssump log + run t{bonds = newBndMap } rc (Just ads) calls log ResetAccRate d accName -> do @@ -543,7 +540,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= 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 + run t{accounts = newAccMap} rc (Just ads) calls log BuildReport sd ed -> let @@ -552,21 +549,19 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= do bsReport <- Rpt.buildBalanceSheet t ed let newlog = FinancialReport sd ed bsReport cashReport - run t poolFlowMap (Just ads) rates calls rAssump $ DL.snoc log newlog -- `debug` ("new log"++ show ed++ show newlog) - + run t rc (Just ads) calls log -- `debug` ("new log"++ show ed++ show newlog) FireTrigger d cyc n -> do - let runContext = RunContext poolFlowMap rAssump rates theTrigger <- case (Map.lookup cyc =<< mTrgMap) >>= Map.lookup n of Nothing -> Left $ "Failed to find trigger "++ n ++" at "++ show cyc ++" for manual fireTrigger" Just trg -> return trg - (newT, rc@(RunContext newPool _ _), adsFromTrigger, newLogsFromTrigger) <- runEffects (t, runContext, ads, DL.empty) d (trgEffects theTrigger) + (newT, rc@(RunContext newPool newRAssump _), adsFromTrigger, newLogsFromTrigger) <- runEffects (t, rc, ads, DL.empty) d (trgEffects theTrigger) let (oldStatus,newStatus) = (status t,status newT) let stChangeLogs = DL.fromList [DealStatusChangeTo d oldStatus newStatus "by Manual fireTrigger" | oldStatus /= newStatus] let triggerFired = case mTrgMap of Nothing -> error "trigger is empty for override" Just tm -> Map.adjust (Map.adjust (set trgStatusLens True) n) cyc tm - run newT {triggers = Just triggerFired} newPool (Just ads) rates calls rAssump $ DL.concat [log,stChangeLogs,newLogsFromTrigger] + run newT {triggers = Just triggerFired} (RunContext newPool newRAssump rates) (Just ads) calls log MakeWhole d spd walTbl -> let @@ -581,27 +576,27 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= (queryCompound t d (FutureCurrentPoolBegBalance Nothing)) (queryCompound t d (FutureCurrentSchedulePoolBegBalance Nothing)) let reduceCfs = Map.map (\f -> (over CF.cashflowTxn (\xs -> CF.scaleTsRow factor <$> xs) f, Nothing ) ) schedulePoolFlowMap -- need to apply with factor and trucate with date - (runDealWithSchedule,_,_) <- run t reduceCfs (Just ads) rates calls rAssump log + (runDealWithSchedule,_,_) <- run t (RunContext reduceCfs rAssump rates) (Just ads) calls log let bondWal = Map.map (L.calcWalBond d) (bonds runDealWithSchedule) -- `debug` ("Bond schedule flow"++ show (bonds runDealWithSchedule)) let bondSprd = Map.map - (\x -> (spd + (fromMaybe 0 (lookupTable walTbl Up (fromRational x >))))) - bondWal + (\x -> (spd + (fromMaybe 0 (lookupTable walTbl Up (fromRational x >))))) + bondWal let bondPricingCurve = Map.map - (\x -> IRateCurve [ TsPoint d x,TsPoint (getDate (last ads)) x]) - bondSprd + (\x -> IRateCurve [ TsPoint d x,TsPoint (getDate (last ads)) x]) + bondSprd bondPricingResult <- sequenceA $ Map.intersectionWith (flip (L.priceBond d)) (bonds runDealWithSchedule) bondPricingCurve depositBondFlow <- sequenceA $ - Map.intersectionWith - (\bnd (PriceResult pv _ _ _ _ _ _) -> - let - ostBal = L.getCurBalance bnd - prinToPay = min pv ostBal - intToPay = max 0 (pv - prinToPay) - in - (pay d DuePrincipal prinToPay) =<< (pay d DueResidual intToPay bnd)) - bndMap - bondPricingResult - run t {bonds = depositBondFlow, status = Ended (Just d)} Map.empty (Just []) rates calls rAssump $ DL.snoc log (EndRun (Just d) "MakeWhole call") + Map.intersectionWith + (\bnd (PriceResult pv _ _ _ _ _ _) -> + let + ostBal = L.getCurBalance bnd + prinToPay = min pv ostBal + intToPay = max 0 (pv - prinToPay) + in + (pay d DuePrincipal prinToPay) =<< (pay d DueResidual intToPay bnd)) + bndMap + bondPricingResult + run t {bonds = depositBondFlow, status = Ended (Just d)} (RunContext Map.empty rAssump rates) (Just []) calls log FundBond d Nothing bName accName fundAmt -> let @@ -610,7 +605,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= do bndFunded <- draw d fundAmt (FundWith bName fundAmt) $ bndMap Map.! bName run t{accounts = newAcc, bonds = Map.insert bName bndFunded bndMap} - poolFlowMap (Just ads) rates calls rAssump log + rc (Just ads) calls log FundBond d (Just p) bName accName fundAmt -> let @@ -619,22 +614,22 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= do flag <- testPre d t p case flag of - False -> run t poolFlowMap (Just ads) rates calls rAssump (DL.snoc log (WarningMsg ("Failed to fund bond"++ bName++ ":" ++show p))) + False -> run t rc (Just ads) calls log True -> do bndFunded <- draw d fundAmt (FundWith bName fundAmt) $ bndMap Map.! bName run t{accounts = newAcc, bonds = Map.insert bName bndFunded bndMap} - poolFlowMap (Just ads) rates calls rAssump log + rc (Just ads) calls log IssueBond d Nothing bGroupName accName bnd mBal mRate -> - run t poolFlowMap (Just ((IssueBond d (Just (Always True)) bGroupName accName bnd mBal mRate):ads)) rates calls rAssump log + run t rc (Just ((IssueBond d (Just (Always True)) bGroupName accName bnd mBal mRate):ads)) calls log IssueBond d (Just p) bGroupName accName bnd mBal mRate -> do flag <- testPre d t p case flag of - False -> run t poolFlowMap (Just ads) rates calls rAssump (DL.snoc log (WarningMsg ("Failed to issue to bond group"++ bGroupName++ ":" ++show p))) + False -> run t rc (Just ads) calls log True -> let newBndName = L.bndName bnd in @@ -666,7 +661,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= (A.deposit issuanceProceeds d (IssuanceProceeds newBndName)) accName accMap - run t{bonds = newBonds, accounts = newAcc} poolFlowMap (Just ads) rates calls rAssump log + run t{bonds = newBonds, accounts = newAcc} rc (Just ads) calls log RefiBondRate d accName bName iInfo -> let lstDate = getDate (last ads) @@ -687,7 +682,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newAccMap <- adjustM (draw d actualPayout (PayInt [bName])) accName accMap let newBndMap = Map.insert bName (newBnd {L.bndRate = newRate, L.bndDueIntDate = Just d ,L.bndLastIntPay = Just d}) bndMap let newAds = sortBy sortActionOnDate $ filteredAds ++ bResetActions - run t{bonds = newBndMap, accounts = newAccMap} poolFlowMap (Just newAds) rates calls rAssump log + run t{bonds = newBndMap, accounts = newAccMap} rc (Just newAds) calls log RefiBond d accName bnd -> Left "Undefined action: RefiBond" @@ -700,25 +695,24 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= case any id flags of True -> let - runContext = RunContext poolFlowMap rAssump rates newStLogs | null cleanUpActions = DL.fromList [DealStatusChangeTo d dStatus Called "by Date-Based Call"] | otherwise = DL.fromList [DealStatusChangeTo d dStatus Called "by Date-Based Call", RunningWaterfall d W.CleanUp] in do - (dealAfterCleanUp, rc_, newLogWaterfall_ ) <- foldM (performActionWrap d) (t, runContext, log) cleanUpActions + (dealAfterCleanUp, rc_, newLogWaterfall_ ) <- foldM (performActionWrap d) (t, rc, log) cleanUpActions endingLogs <- Rpt.patchFinancialReports dealAfterCleanUp d newLogWaterfall_ return (dealAfterCleanUp , DL.snoc (endingLogs `DL.append` newStLogs) (EndRun (Just d) "Clean Up") , (runPoolFlow rc_)) - _ -> run t poolFlowMap (Just ads) rates calls rAssump log + _ -> run t rc (Just ads) calls log StopRunTest d pres -> do flags::[Bool] <- sequenceA $ [ (testPre d t pre) | pre <- pres ] case all id flags of True -> return (t, DL.snoc log (EndRun (Just d) ("Stop Run Test by:"++ show (zip pres flags))), poolFlowMap) - _ -> run t poolFlowMap (Just ads) rates calls rAssump log + _ -> run t rc (Just ads) calls log _ -> Left $ "Failed to match action on Date"++ show ad @@ -728,4 +722,4 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= remainCollectionNum = Map.elems $ Map.map (\(x,_) -> CF.sizeCashFlowFrame x ) poolFlowMap futureCashToCollectFlag = and $ Map.elems $ Map.map (\(pcf,_) -> all CF.isEmptyRow2 (view CF.cashflowTxn pcf)) poolFlowMap -run t empty _ _ _ _ log = return (t, log ,empty) -- `debug` ("End with pool CF is []") +run t (RunContext empty _ _) _ _ log = return (t, log ,empty) -- `debug` ("End with pool CF is []") From a8d69e883130b4558dbc9904f6dbe9e0b231f7a4 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Tue, 3 Mar 2026 20:00:27 +0800 Subject: [PATCH 12/17] Add RunContext to deal run function --- src/Deal.hs | 2 +- src/Deal/DealAction.hs | 582 +++++++++++++++++++------------------- src/Deal/DealBase.hs | 10 + src/Deal/DealQuery.hs | 227 ++++++++------- src/Deal/DealRun.hs | 92 +++--- src/Reports.hs | 27 +- test/DealTest/DealTest.hs | 159 ++++++----- test/UT/AccountTest.hs | 103 +++---- test/UT/DealTest.hs | 10 +- test/UT/DealTest2.hs | 52 ++-- test/UT/ExpTest.hs | 47 +-- test/UT/RateHedgeTest.hs | 17 +- 12 files changed, 677 insertions(+), 651 deletions(-) diff --git a/src/Deal.hs b/src/Deal.hs index a37a98ec..6ce5f192 100644 --- a/src/Deal.hs +++ b/src/Deal.hs @@ -237,7 +237,7 @@ priceBonds t@TestDeal {bonds = bndMap} (AP.IrrInput bMapInput) -- , runDeal :: Ast.Asset a => TestDeal a -> S.Set ExpectReturn -> Maybe AP.ApplyAssumptionType-> AP.NonPerfAssumption - -> Either String (TestDeal a + -> Either ErrorRep (TestDeal a , Map.Map PoolId CF.CashFlowFrame , [ResultComponent] , Map.Map String PriceResult diff --git a/src/Deal/DealAction.hs b/src/Deal/DealAction.hs index 165f259d..bf894275 100644 --- a/src/Deal/DealAction.hs +++ b/src/Deal/DealAction.hs @@ -68,11 +68,11 @@ import Control.Monad debug = flip trace -- ^ Test triggers -testTrigger :: Ast.Asset a => TestDeal a -> Date -> Trigger -> Either ErrorRep Trigger -testTrigger t d trigger@Trigger{trgStatus=st,trgCurable=curable,trgCondition=cond,trgStmt = tStmt} +testTrigger :: Ast.Asset a => TestDeal a -> RunContext -> Date -> Trigger -> Either ErrorRep Trigger +testTrigger t rc d trigger@Trigger{trgStatus=st,trgCurable=curable,trgCondition=cond,trgStmt = tStmt} | not curable && st = return trigger | otherwise = let - (memo, newStM) = testPre2 d t cond + (memo, newStM) = testPre2 d t rc cond in do newSt <- newStM @@ -100,41 +100,41 @@ allocAmtToBonds theOrder amt bndsWithDue = $ paySeqLiabilitiesAmt amt orderedAmt -calcDueFee :: Ast.Asset a => TestDeal a -> Date -> F.Fee -> Either ErrorRep F.Fee +calcDueFee :: Ast.Asset a => TestDeal a -> RunContext -> Date -> F.Fee -> Either ErrorRep F.Fee -- ^ one-off fee, can be accrued multiple times -calcDueFee t calcDay f@(F.Fee fn (F.FixFee amt) fs fd fdDay fa _ _) +calcDueFee t rc calcDay f@(F.Fee fn (F.FixFee amt) fs fd fdDay fa _ _) | isJust fdDay = return f -- Nothing change if it has been calculated before | calcDay >= fs && isNothing fdDay = return $ f { F.feeDue = amt, F.feeDueDate = Just calcDay} | otherwise = return f -- ^ annualised fee: patch start date as last fee due date -calcDueFee t calcDay f@(F.Fee fn (F.AnnualRateFee feeBase r) fs fd Nothing fa lpd _) - | calcDay >= fs = calcDueFee t calcDay f {F.feeDueDate = Just fs } +calcDueFee t rc calcDay f@(F.Fee fn (F.AnnualRateFee feeBase r) fs fd Nothing fa lpd _) + | calcDay >= fs = calcDueFee t rc calcDay f {F.feeDueDate = Just fs } | otherwise = return f -- ^ annualized % fee base on pool balance amount -calcDueFee t calcDay f@(F.Fee fn (F.AnnualRateFee feeBase _r) fs fd (Just fdDay) fa lpd _) +calcDueFee t rc calcDay f@(F.Fee fn (F.AnnualRateFee feeBase _r) fs fd (Just fdDay) fa lpd _) = let patchedDs = patchDatesToStats t fdDay calcDay feeBase in do - r <- queryCompound t calcDay _r - baseBal <- queryCompound t calcDay patchedDs + r <- queryCompound t rc calcDay _r + baseBal <- queryCompound t rc calcDay patchedDs let newDue = baseBal * r return f { F.feeDue= fd + fromRational newDue, F.feeDueDate = Just calcDay } -- ^ percentage fee base on a formula rate -calcDueFee t calcDay f@(F.Fee fn (F.PctFee ds _r ) fs fd fdDay fa lpd _) +calcDueFee t rc calcDay f@(F.Fee fn (F.PctFee ds _r ) fs fd fdDay fa lpd _) | calcDay <= fs = return f | otherwise = do - r <- queryCompound t calcDay _r - baseBal <- queryCompound t calcDay (patchDateToStats calcDay ds) + r <- queryCompound t rc calcDay _r + baseBal <- queryCompound t rc calcDay (patchDateToStats calcDay ds) return f { F.feeDue = fd + fromRational (baseBal * r), F.feeDueDate = Just calcDay } -- ^ time series based fee, can be accrued multiple times -calcDueFee t calcDay f@(F.Fee fn (F.FeeFlow ts) fs fd _ fa mflpd _) +calcDueFee t rc calcDay f@(F.Fee fn (F.FeeFlow ts) fs fd _ fa mflpd _) = let (currentNewDue,futureDue) = splitTsByDate ts calcDay cumulativeDue = sumValTs currentNewDue @@ -143,7 +143,7 @@ calcDueFee t calcDay f@(F.Fee fn (F.FeeFlow ts) fs fd _ fa mflpd _) return f{ F.feeDue = newFeeDue ,F.feeDueDate = Just calcDay ,F.feeType = F.FeeFlow futureDue} -- ^ fee based on a recurring date pattern, exempt by reAccruableFeeType check -calcDueFee t calcDay f@(F.Fee fn (F.RecurFee p amt) fs fd mLastAccDate fa _ _) +calcDueFee t rc calcDay f@(F.Fee fn (F.RecurFee p amt) fs fd mLastAccDate fa _ _) | periodGaps == 0 = return f | otherwise = return f { F.feeDue = amt * fromIntegral periodGaps + fd , F.feeDueDate = Just (T.addDays 1 calcDay) } @@ -154,16 +154,16 @@ calcDueFee t calcDay f@(F.Fee fn (F.RecurFee p amt) fs fd mLastAccDate fa _ _) periodGaps = length accDates -- ^ fee based on an integer number, exempt by reAccruableFeeType check -calcDueFee t calcDay f@(F.Fee fn (F.NumFee p s amt) fs fd Nothing fa lpd _) - | calcDay >= fs = calcDueFee t calcDay f {F.feeDueDate = Just fs } +calcDueFee t rc calcDay f@(F.Fee fn (F.NumFee p s amt) fs fd Nothing fa lpd _) + | calcDay >= fs = calcDueFee t rc calcDay f {F.feeDueDate = Just fs } | otherwise = return f -- ^ fee based on an integer number, exempt by reAccruableFeeType check -calcDueFee t calcDay f@(F.Fee fn (F.NumFee p s amt) fs fd (Just fdDay) fa lpd _) +calcDueFee t rc calcDay f@(F.Fee fn (F.NumFee p s amt) fs fd (Just fdDay) fa lpd _) | fdDay == calcDay = return f | periodGap == 0 = return f | otherwise = do - baseCount <- queryCompound t calcDay (patchDateToStats calcDay s) + baseCount <- queryCompound t rc calcDay (patchDateToStats calcDay s) let newFeeDueAmt = (fromRational baseCount) * amt * fromIntegral periodGap -- `debug` ("amt"++show amt++">>"++show baseCount++">>"++show periodGap) return f { F.feeDue = fd+newFeeDueAmt , F.feeDueDate = Just calcDay } where @@ -171,15 +171,15 @@ calcDueFee t calcDay f@(F.Fee fn (F.NumFee p s amt) fs fd (Just fdDay) fa lpd _) periodGap = length dueDates -- `debug` ("Due Dates"++ show dueDates) -- ^ fee based on target balance difference -calcDueFee t calcDay f@(F.Fee fn (F.TargetBalanceFee dsDue dsPaid) fs fd _ fa lpd _) +calcDueFee t rc calcDay f@(F.Fee fn (F.TargetBalanceFee dsDue dsPaid) fs fd _ fa lpd _) = do let dsDueD = patchDateToStats calcDay dsDue let dsPaidD = patchDateToStats calcDay dsPaid - dueAmt <- max 0 <$> (liftA2) (-) (queryCompound t calcDay dsDueD) (queryCompound t calcDay dsPaidD) + dueAmt <- max 0 <$> (liftA2) (-) (queryCompound t rc calcDay dsDueD) (queryCompound t rc calcDay dsPaidD) return f { F.feeDue = fromRational dueAmt, F.feeDueDate = Just calcDay} -- ^ fee based on a collection period -calcDueFee t@TestDeal{ pool = pool } calcDay f@(F.Fee fn (F.ByCollectPeriod amt) fs fd fdday fa lpd _) +calcDueFee t@TestDeal{ pool = pool } rc calcDay f@(F.Fee fn (F.ByCollectPeriod amt) fs fd fdday fa lpd _) = let txnsDates = getDate <$> getAllCollectedTxnsList t (Just [PoolConsol]) pastPeriods = case fdday of @@ -190,30 +190,30 @@ calcDueFee t@TestDeal{ pool = pool } calcDay f@(F.Fee fn (F.ByCollectPeriod amt) return $ f {F.feeDue = dueAmt + fd, F.feeDueDate = Just calcDay} -- ^ fee based on a table lookup, exempt by reAccruableFeeType check -calcDueFee t calcDay f@(F.Fee fn (F.AmtByTbl _ ds tbl) fs fd Nothing fa lpd _) - = calcDueFee t calcDay f {F.feeDueDate = Just fs } +calcDueFee t rc calcDay f@(F.Fee fn (F.AmtByTbl _ ds tbl) fs fd Nothing fa lpd _) + = calcDueFee t rc calcDay f {F.feeDueDate = Just fs } -calcDueFee t calcDay f@(F.Fee fn (F.AmtByTbl _ ds tbl) fs fd (Just fdday) fa lpd _) +calcDueFee t rc calcDay f@(F.Fee fn (F.AmtByTbl _ ds tbl) fs fd (Just fdday) fa lpd _) | fdday == calcDay = return f | otherwise = do - lookupVal <- queryCompound t calcDay (patchDateToStats calcDay ds) + lookupVal <- queryCompound t rc calcDay (patchDateToStats calcDay ds) let dueAmt = fromMaybe 0.0 $ lookupTable tbl Up (fromRational lookupVal >=) return f {F.feeDue = dueAmt + fd, F.feeDueDate = Just calcDay} -- ^ fee based on a pool period number -calcDueFee t calcDay f@(F.Fee fn (F.FeeFlowByPoolPeriod pc) fs fd fdday fa lpd stmt) +calcDueFee t rc 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]) + currentPoolPeriod <- queryCompound t rc calcDay (DealStatInt PoolCollectedPeriod) + feePaidAmt <- queryCompound t rc 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} -- ^ fee based on a bond period number -calcDueFee t calcDay f@(F.Fee fn (F.FeeFlowByBondPeriod pc) fs fd fdday fa lpd stmt) +calcDueFee t rc 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]) + currentBondPeriod <- queryCompound t rc calcDay (DealStatInt BondPaidPeriod) + feePaidAmt <- queryCompound t rc 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} @@ -227,54 +227,54 @@ disableLiqProvider _ d liq@CE.LiqFacility{CE.liqEnds = Nothing } = liq -- refresh available balance ---- for Replenish Support and ByPct -updateLiqProvider :: Ast.Asset a => TestDeal a -> Date -> CE.LiqFacility -> CE.LiqFacility -updateLiqProvider t d liq@CE.LiqFacility{CE.liqType = liqType, CE.liqCredit = curCredit} +updateLiqProvider :: Ast.Asset a => TestDeal a -> RunContext -> Date -> CE.LiqFacility -> CE.LiqFacility +updateLiqProvider t rc d liq@CE.LiqFacility{CE.liqType = liqType, CE.liqCredit = curCredit} = disableLiqProvider t d $ liq { CE.liqCredit = newCredit } where -- TODO ,need to remove due int and due fee newCredit = case liqType of -- CE.ReplenishSupport _ b -> max b <$> curCredit - CE.ByPct ds _r -> case (* _r) <$> (queryCompound t d (patchDateToStats d ds)) of + CE.ByPct ds _r -> case (* _r) <$> (queryCompound t rc d (patchDateToStats d ds)) of Left y -> error "Shouldn't happen" Right x -> updateSupportAvailType (min (fromRational x)) curCredit _ -> curCredit -- ^TODO : to be replace from L.accrueInt -- Not possible to use L.accrueInt, since the interest may use formula to query on deal's stats -calcDueInt :: Ast.Asset a => TestDeal a -> Date -> L.Bond -> Either ErrorRep L.Bond -calcDueInt t d b@(L.BondGroup bMap pt) +calcDueInt :: Ast.Asset a => TestDeal a -> RunContext -> Date -> L.Bond -> Either ErrorRep L.Bond +calcDueInt t rc d b@(L.BondGroup bMap pt) = do - m <- mapM (calcDueInt t d) bMap + m <- mapM (calcDueInt t rc d) bMap return $ L.BondGroup m pt -- first time to accrue interest\ -- use default date to start to accrue -calcDueInt t@TestDeal{ status = st, dates = dealDates} d b@(L.Bond bn bt oi io _ bal r dp _ di Nothing _ _ _ ) +calcDueInt t@TestDeal{ status = st, dates = dealDates} rc d b@(L.Bond bn bt oi io _ bal r dp _ di Nothing _ _ _ ) | bal+di == 0 && (bt /= L.IO) = return b | otherwise = do sd <- getClosingDate dealDates - b' <- calcDueInt t d (b {L.bndDueIntDate = Just sd }) + b' <- calcDueInt t rc d (b {L.bndDueIntDate = Just sd }) return b' -- Interest Only Bond with Reference Balance -calcDueInt t d b@(L.Bond _ L.IO oi (L.RefBal refBal ii) _ bal r dp dInt dioi (Just lastIntDueDay) _ _ _ ) +calcDueInt t rc d b@(L.Bond _ L.IO oi (L.RefBal refBal ii) _ bal r dp dInt dioi (Just lastIntDueDay) _ _ _ ) = do - balUsed <- queryCompound t d refBal -- `debug` ("Hit acc int"++show d ++" bond name"++ L.bndName b) + balUsed <- queryCompound t rc d refBal -- `debug` ("Hit acc int"++show d ++" bond name"++ L.bndName b) let newDueInt = IR.calcInt (fromRational balUsed) lastIntDueDay d r (fromMaybe DC_ACT_365F (L.getDayCountFromInfo ii)) -- `debug` ("Balused" ++ show (fromRational balUsed) ++ "lastIntDueDay"++show lastIntDueDay ++ "d"++show d ++ "r"++show r) return b { L.bndDueInt = newDueInt + dInt, L.bndDueIntDate = Just d } -- Z bond -calcDueInt t d b@(L.Bond bn L.Z bo bi _ bond_bal bond_rate _ _ _ _ lstIntPay _ _) +calcDueInt t rc d b@(L.Bond bn L.Z bo bi _ bond_bal bond_rate _ _ _ _ lstIntPay _ _) = return $ b {L.bndDueInt = 0 } -- Won't accrue interest for Equity bond -calcDueInt t d b@(L.Bond _ L.Equity _ _ _ _ _ _ _ _ _ _ _ _) - = return b +calcDueInt t rc d b@(L.Bond _ L.Equity _ _ _ _ _ _ _ _ _ _ _ _) + = return b -- accrued with interest over interest -calcDueInt t d b@(L.Bond bn bt bo (L.WithIoI intInfo ioiIntInfo) _ bond_bal bond_rate _ intDue ioiIntDue (Just int_due_date) lstIntPay _ _ ) +calcDueInt t rc d b@(L.Bond bn bt bo (L.WithIoI intInfo ioiIntInfo) _ bond_bal bond_rate _ intDue ioiIntDue (Just int_due_date) lstIntPay _ _ ) = let ioiRate = case ioiIntInfo of @@ -285,34 +285,34 @@ calcDueInt t d b@(L.Bond bn bt bo (L.WithIoI intInfo ioiIntInfo) _ bond_bal bond newBond = b { L.bndDueIntOverInt = ioiInt, L.bndInterestInfo = intInfo } in do - newBondWithIntInfo <- calcDueInt t d newBond + newBondWithIntInfo <- calcDueInt t rc d newBond return newBondWithIntInfo { L.bndInterestInfo = L.WithIoI intInfo ioiIntInfo} -- TODO: to enable override rate & balance -- accure interest by rate -calcDueInt t d b@(L.MultiIntBond {}) = return $ L.accrueInt d b +calcDueInt t rc d b@(L.MultiIntBond {}) = return $ L.accrueInt d b -calcDueInt t d b@(L.Bond {}) = return $ L.accrueInt d b +calcDueInt t rc d b@(L.Bond {}) = return $ L.accrueInt d b -- ^ modify due principal for bond -calcDuePrin :: Ast.Asset a => TestDeal a -> Date -> L.Bond -> Either ErrorRep L.Bond -calcDuePrin t d b@(L.BondGroup bMap pt) +calcDuePrin :: Ast.Asset a => TestDeal a -> RunContext -> Date -> L.Bond -> Either ErrorRep L.Bond +calcDuePrin t rc d b@(L.BondGroup bMap pt) = do - m <- sequenceA $ Map.map (calcDuePrin t d) bMap + m <- sequenceA $ Map.map (calcDuePrin t rc d) bMap return $ L.BondGroup m pt -calcDuePrin t d b = +calcDuePrin t rc d b = let bondBal = L.bndBalance b in do - tBal <- calcBondTargetBalance t d b + tBal <- calcBondTargetBalance t rc d b return $ b {L.bndDuePrin = max 0 (bondBal - tBal) } -- ^ accure rate cap -accrueRC :: Ast.Asset a => TestDeal a -> Date -> [RateAssumption] -> HE.RateCap -> Either ErrorRep HE.RateCap -accrueRC t d rs rc@HE.RateCap{HE.rcNetCash = amt, HE.rcStrikeRate = strike,HE.rcIndex = index +accrueRC :: Ast.Asset a => TestDeal a -> RunContext -> Date -> [RateAssumption] -> HE.RateCap -> Either ErrorRep HE.RateCap +accrueRC t rctx d rs rc@HE.RateCap{HE.rcNetCash = amt, HE.rcStrikeRate = strike,HE.rcIndex = index ,HE.rcStartDate = sd, HE.rcEndDate = ed, HE.rcNotional = notional ,HE.rcLastStlDate = mlsd ,HE.rcStmt = mstmt} @@ -320,14 +320,14 @@ accrueRC t d rs rc@HE.RateCap{HE.rcNetCash = amt, HE.rcStrikeRate = strike,HE.rc | otherwise = do r <- AP.lookupRate0 rs index d balance <- case notional of - HE.Fixed bal -> Right . toRational $ bal - HE.Base ds -> queryCompound t d (patchDateToStats d ds) - HE.Schedule ts -> return $ getValByDate ts Inc d + HE.Fixed bal -> Right . toRational $ bal + HE.Base ds -> queryCompound t rctx d (patchDateToStats d ds) + HE.Schedule ts -> return $ getValByDate ts Inc d let accRate = max 0 $ r - fromRational (getValByDate strike Inc d) -- `debug` ("Rate from curve"++show (getValByDate strike Inc d)) let addAmt = case mlsd of - Nothing -> IR.calcInt (fromRational balance) sd d accRate DC_ACT_365F - Just lstD -> IR.calcInt (fromRational balance) lstD d accRate DC_ACT_365F + Nothing -> IR.calcInt (fromRational balance) sd d accRate DC_ACT_365F + Just lstD -> IR.calcInt (fromRational balance) lstD d accRate DC_ACT_365F let newAmt = amt + addAmt -- `debug` ("Accrue AMT"++ show addAmt) let newStmt = appendStmt (IrsTxn d newAmt addAmt 0 0 0 SwapAccrue) mstmt @@ -335,17 +335,18 @@ accrueRC t d rs rc@HE.RateCap{HE.rcNetCash = amt, HE.rcStrikeRate = strike,HE.rc -- ^ accrue all liabilities of deal to date d -accrueDeal :: Ast.Asset a => Date -> [RateAssumption] -> TestDeal a -> Either ErrorRep (TestDeal a) +accrueDeal :: Ast.Asset a => Date -> [RateAssumption] -> TestDeal a -> RunContext -> Either ErrorRep (TestDeal a) accrueDeal d ras t@TestDeal{fees = feeMap, bonds = bondMap, liqProvider = liqMap , rateSwap = rsMap, rateCap = rcMap, accounts = accMap} + rc = let liqMap' = (Map.map (CE.accrueLiqProvider d)) <$> liqMap rsMap' = (Map.map (HE.accrueIRS d)) <$> rsMap in do - bondMap' <- sequenceA (Map.map (calcDueInt t d) bondMap) - feeMap' <- sequenceA $ Map.map (\v -> if (F.reAccruableFeeType (F.feeType v)) then calcDueFee t d v else pure v) feeMap - rcMap' <- traverse (Map.traverseWithKey (\_ -> accrueRC t d ras)) rcMap + bondMap' <- sequenceA (Map.map (calcDueInt t rc d) bondMap) + feeMap' <- sequenceA $ Map.map (\v -> if (F.reAccruableFeeType (F.feeType v)) then calcDueFee t rc d v else pure v) feeMap + rcMap' <- traverse (Map.traverseWithKey (\_ -> accrueRC t rc d ras)) rcMap return t { fees = feeMap' , bonds = bondMap', liqProvider = liqMap', @@ -412,12 +413,7 @@ buyRevolvingPool d r rp@(AssetCurve aus) (assetBought, rp) -data RunContext = RunContext{ - runPoolFlow:: Map.Map PoolId CF.PoolCashflow - ,revolvingAssump:: Maybe (Map.Map String (RevolvingPool ,AP.ApplyAssumptionType)) - ,revolvingInterestRateAssump:: Maybe [RateAssumption] - } - deriving (Show) + updateOriginDate2 :: Date -> ACM.AssetUnion -> ACM.AssetUnion updateOriginDate2 d (ACM.LO m) = ACM.LO $ updateOriginDate m (Ast.calcAlignDate m d) @@ -437,32 +433,32 @@ sumSupport x = error $ "sumSupport: unsupported type " ++ show x -- ^ get available supports in balance -evalExtraSupportBalance :: Ast.Asset a => Date -> TestDeal a -> W.ExtraSupport -> Either ErrorRep SupportAvailType -evalExtraSupportBalance d t (W.WithCondition pre s) +evalExtraSupportBalance :: Ast.Asset a => Date -> TestDeal a -> RunContext -> W.ExtraSupport -> Either ErrorRep SupportAvailType +evalExtraSupportBalance d t rc (W.WithCondition pre s) = do - flag <- testPre d t pre + flag <- testPre d t rc pre if flag then - evalExtraSupportBalance d t s + evalExtraSupportBalance d t rc s else return $ ByAvailAmount 0 -evalExtraSupportBalance d t@TestDeal{accounts=accMap} (W.SupportAccount an _) +evalExtraSupportBalance d t@TestDeal{accounts=accMap} rc (W.SupportAccount an _) = do acc <- lookupM an accMap return $ ByAvailAmount $ A.accBalance acc -evalExtraSupportBalance d t@TestDeal{liqProvider=Just liqMap} (W.SupportLiqFacility liqName) +evalExtraSupportBalance d t@TestDeal{liqProvider=Just liqMap} rc (W.SupportLiqFacility liqName) = do support <- lookupM liqName liqMap case Map.lookup liqName liqMap of Nothing -> Left $ "Liquidity facility not found:" ++ show liqName Just liq -> return $ CE.liqCredit liq -evalExtraSupportBalance d t (W.MultiSupport supports) - = sumSupport <$> (sequenceA [ (evalExtraSupportBalance d t sp) | sp <- supports ]) +evalExtraSupportBalance d t rc (W.MultiSupport supports) + = sumSupport <$> (sequenceA [ (evalExtraSupportBalance d t rc sp) | sp <- supports ]) -- ^ draw support from a deal , return updated deal,and remaining oustanding amount -drawExtraSupport :: Ast.Asset a => Date -> Amount -> W.ExtraSupport -> TestDeal a -> Either ErrorRep (TestDeal a, Amount) +drawExtraSupport :: Ast.Asset a => Date -> Amount -> W.ExtraSupport -> TestDeal a -> RunContext -> Either ErrorRep (TestDeal a, Amount) -- ^ draw account support and book ledger -drawExtraSupport d amt (W.SupportAccount an (Just (dr, ln))) t@TestDeal{accounts=accMap, ledgers= Just ledgerMap} +drawExtraSupport d amt (W.SupportAccount an (Just (dr, ln))) t@TestDeal{accounts=accMap, ledgers= Just ledgerMap} rc = do acc <- lookupM an accMap let drawAmt = min (A.accBalance acc) amt @@ -473,7 +469,7 @@ drawExtraSupport d amt (W.SupportAccount an (Just (dr, ln))) t@TestDeal{accounts , oustandingAmt) -- ^ draw account support -drawExtraSupport d amt (W.SupportAccount an Nothing) t@TestDeal{accounts=accMap} +drawExtraSupport d amt (W.SupportAccount an Nothing) t@TestDeal{accounts=accMap} rc = do acc <- lookupM an accMap let drawAmt = min (A.accBalance acc) amt @@ -482,7 +478,7 @@ drawExtraSupport d amt (W.SupportAccount an Nothing) t@TestDeal{accounts=accMap} return (t {accounts = newAccMap } , oustandingAmt) -- ^ draw support from liquidity facility -drawExtraSupport d amt (W.SupportLiqFacility liqName) t@TestDeal{liqProvider= Just liqMap} +drawExtraSupport d amt (W.SupportLiqFacility liqName) t@TestDeal{liqProvider= Just liqMap} rc = do theLiqProvider <- lookupM liqName liqMap let drawAmt = case CE.liqCredit theLiqProvider of @@ -493,38 +489,38 @@ drawExtraSupport d amt (W.SupportLiqFacility liqName) t@TestDeal{liqProvider= Ju return (t {liqProvider = Just newLiqMap} , oustandingAmt) -- ^ draw multiple supports by sequence -drawExtraSupport d amt (W.MultiSupport supports) t +drawExtraSupport d amt (W.MultiSupport supports) t rc = foldM - (\(deal,remainAmt) support -> drawExtraSupport d remainAmt support deal) + (\(deal,remainAmt) support -> drawExtraSupport d remainAmt support deal rc) (t, amt) supports -drawExtraSupport d amt (W.WithCondition pre s) t +drawExtraSupport d amt (W.WithCondition pre s) t rc = do - flag <- testPre d t pre + flag <- testPre d t rc pre if flag then - drawExtraSupport d amt s t + drawExtraSupport d amt s t rc else return (t, amt) -inspectListVars :: Ast.Asset a => TestDeal a -> Date -> [DealStats] -> Either ErrorRep [ResultComponent] -inspectListVars t d dss = sequenceA [ inspectVars t d ds | ds <- dss] +inspectListVars :: Ast.Asset a => TestDeal a -> RunContext -> Date -> [DealStats] -> Either ErrorRep [ResultComponent] +inspectListVars t rc d dss = sequenceA [ inspectVars t rc d ds | ds <- dss] -inspectVars :: Ast.Asset a => TestDeal a -> Date -> DealStats -> Either ErrorRep ResultComponent -inspectVars t d ds = +inspectVars :: Ast.Asset a => TestDeal a -> RunContext -> Date -> DealStats -> Either ErrorRep ResultComponent +inspectVars t rc d ds = case getDealStatType ds of RtnRate -> do - q <- queryCompound t d (patchDateToStats d ds) + q <- queryCompound t rc d (patchDateToStats d ds) return $ InspectRate d ds $ fromRational q RtnBool -> do - q <- queryDealBool t (patchDateToStats d ds) d + q <- queryDealBool t rc (patchDateToStats d ds) d return $ InspectBool d ds q RtnInt -> do - q <- queryCompound t d (patchDateToStats d ds) + q <- queryCompound t rc d (patchDateToStats d ds) return $ InspectInt d ds $ round . fromRational $ q _ -> do - q <- queryCompound t d (patchDateToStats d ds) + q <- queryCompound t rc d (patchDateToStats d ds) return $ InspectBal d ds $ fromRational q showInspection :: ResultComponent -> String @@ -535,19 +531,19 @@ showInspection (InspectBal d ds r) = show r showInspection x = error $ "not implemented for showing ResultComponent " ++ show x -calcAvailFund :: Ast.Asset a => TestDeal a -> Date -> A.Account -> Maybe W.ExtraSupport -> Either ErrorRep SupportAvailType -calcAvailFund t d acc Nothing = return $ ByAvailAmount $ A.accBalance acc -calcAvailFund t d acc (Just support) = (\x -> sumSupport ((ByAvailAmount (A.accBalance acc)):[x]) ) <$> evalExtraSupportBalance d t support +calcAvailFund :: Ast.Asset a => TestDeal a -> RunContext -> Date -> A.Account -> Maybe W.ExtraSupport -> Either ErrorRep SupportAvailType +calcAvailFund t rc d acc Nothing = return $ ByAvailAmount $ A.accBalance acc +calcAvailFund t rc d acc (Just support) = (\x -> sumSupport ((ByAvailAmount (A.accBalance acc)):[x]) ) <$> evalExtraSupportBalance d t rc support -- ^ Deal, Date , cap balance, due balance -applyLimit :: Ast.Asset a => TestDeal a -> Date -> Balance -> Balance -> Maybe Limit -> Either ErrorRep Balance -applyLimit t d availBal dueBal Nothing = return $ min availBal dueBal -applyLimit t d availBal dueBal (Just limit) = +applyLimit :: Ast.Asset a => TestDeal a -> RunContext -> Date -> Balance -> Balance -> Maybe Limit -> Either ErrorRep Balance +applyLimit t rc d availBal dueBal Nothing = return $ min availBal dueBal +applyLimit t rc d availBal dueBal (Just limit) = (min dueBal) <$> case limit of DueCapAmt amt -> return $ min amt availBal DS ds -> do - v <- queryCompound t d (patchDateToStats d ds) + v <- queryCompound t rc d (patchDateToStats d ds) return (min (fromRational v) availBal) DuePct pct -> return $ min availBal $ mulBR dueBal pct @@ -556,14 +552,14 @@ applyLimit t d availBal dueBal (Just limit) = -- Get paid out amount after constrain of support and limit -- Return (PaidOutAmount, PaidOut from Account, PaidOut from Support) -calcAvailAfterLimit :: Ast.Asset a => TestDeal a -> Date -> A.Account -> Maybe W.ExtraSupport +calcAvailAfterLimit :: Ast.Asset a => TestDeal a -> RunContext -> Date -> A.Account -> Maybe W.ExtraSupport -> Balance -> Maybe Limit -> Either ErrorRep (Amount, Amount, Amount) -- No support , No limit -> use min(Account Balance, Due Amount) -calcAvailAfterLimit t d acc Nothing dueAmt Nothing +calcAvailAfterLimit t rc d acc Nothing dueAmt Nothing = return $ (min (A.accBalance acc) dueAmt, min (A.accBalance acc) dueAmt, 0) -- No support , with Limit -> -calcAvailAfterLimit t d acc Nothing dueAmt (Just limit) +calcAvailAfterLimit t rc d acc Nothing dueAmt (Just limit) = let afterDueAmt = min (A.accBalance acc) dueAmt in @@ -571,33 +567,33 @@ calcAvailAfterLimit t d acc Nothing dueAmt (Just limit) txnAmt <- case limit of DueCapAmt amt -> return $ min amt afterDueAmt DS ds -> do - v <- queryCompound t d (patchDateToStats d ds) + v <- queryCompound t rc d (patchDateToStats d ds) return $ min (fromRational v) afterDueAmt DuePct pct -> return $ min (mulBR afterDueAmt pct) afterDueAmt _ -> Left ("Failed to find type"++ show limit) return (txnAmt, txnAmt, 0) -- with support , with Limit -> Get Account Balance -calcAvailAfterLimit t d acc (Just support) dueAmt mLimit +calcAvailAfterLimit t rc d acc (Just support) dueAmt mLimit = let accBal = A.accBalance acc in do - availSupport <- evalExtraSupportBalance d t support + availSupport <- evalExtraSupportBalance d t rc support let totalSupport = sumSupport [availSupport, ByAvailAmount accBal] case totalSupport of Unlimit -> return (dueAmt,dueAmt,0) ByAvailAmount availFund -> do - txnAmt <- applyLimit t d availFund dueAmt mLimit + txnAmt <- applyLimit t rc d availFund dueAmt mLimit return (txnAmt, min txnAmt accBal, max (txnAmt - accBal) 0) -updateSupport :: Ast.Asset a => Date -> Maybe W.ExtraSupport -> Balance -> TestDeal a -> Either ErrorRep (TestDeal a) -updateSupport _ Nothing _ t = return t -updateSupport d (Just support) bal t = +updateSupport :: Ast.Asset a => Date -> Maybe W.ExtraSupport -> Balance -> TestDeal a -> RunContext -> Either ErrorRep (TestDeal a) +updateSupport _ Nothing _ t rc = return t +updateSupport d (Just support) bal t rc = do - (deal,amt) <- drawExtraSupport d bal support t + (deal,amt) <- drawExtraSupport d bal support t rc return deal performActionWrap :: Ast.Asset a => Date -> (TestDeal a, RunContext, DL.DList ResultComponent) @@ -635,7 +631,7 @@ performActionWrap d acc <- lookupM accName accsMap let accBal = A.accBalance acc limitAmt <- case ml of - Just (DS ds) -> queryCompound t d (patchDateToStats d ds) + Just (DS ds) -> queryCompound t rc d (patchDateToStats d ds) Just (DueCapAmt amt) -> return (toRational amt) Just (DuePct pct) -> return $ toRational (mulBR accBal pct) Nothing -> return (toRational accBal) @@ -711,51 +707,51 @@ performActionWrap d ,rc@RunContext{runPoolFlow = pcf} ,logs) (W.LiquidatePool lm an mPid) - = let - liqFunction = \(p@P.Pool{ P.issuanceStat = m} ) - -> over (P.poolFutureScheduleCf . _Just . _1) (CF.extendCashFlow d) $ - over (P.poolFutureCf . _Just . _1 ) (CF.extendCashFlow d) $ - p { P.issuanceStat = Just (Map.insert RuntimeCurrentPoolBalance 0 (fromMaybe Map.empty m)) } - - poolMapToLiq = case (pt, mPid) of - (MultiPool pm, Nothing) -> pm - (MultiPool pm,Just pids) -> let - selectedPids = S.fromList pids - in - Map.filterWithKey (\k v -> S.member k selectedPids) pm - - (ResecDeal _,_) -> error "Not implement on liquidate resec deal" - - - - liqAmtByPool = Map.mapWithKey (\k p -> P.pricingPoolFlow d p (pcf Map.! k) lm) poolMapToLiq -- `debug` ("pool id to liq"++ show poolMapToLiq) - liqAmt = sum $ Map.elems liqAmtByPool - - -- Update collected cashflow - newPt = case (pt, mPid) of - (MultiPool pm, Nothing) -> MultiPool $ Map.map liqFunction pm - (MultiPool pm, Just pids) -> let - selectedPids = S.fromList pids - selectedPoolMap = Map.filterWithKey (\k v -> S.member k selectedPids) pm - in - MultiPool $ Map.union (Map.map liqFunction selectedPoolMap) pm - (ResecDeal _,_) -> error "Not implement on liquidate resec deal" - - liqComment = LiquidationProceeds (fromMaybe [] mPid) - accMapAfterLiq = Map.adjust (A.deposit liqAmt d liqComment) an accMap - newPfInRc = foldr (Map.adjust (set (_1 . CF.cashflowTxn) [])) pcf (Map.keys poolMapToLiq) - -- Update current balance to zero - in - return (t {accounts = accMapAfterLiq , pool = newPt} , rc {runPoolFlow = newPfInRc}, logs) + = let + liqFunction = \(p@P.Pool{ P.issuanceStat = m} ) + -> over (P.poolFutureScheduleCf . _Just . _1) (CF.extendCashFlow d) $ + over (P.poolFutureCf . _Just . _1 ) (CF.extendCashFlow d) $ + p { P.issuanceStat = Just (Map.insert RuntimeCurrentPoolBalance 0 (fromMaybe Map.empty m)) } + + poolMapToLiq = case (pt, mPid) of + (MultiPool pm, Nothing) -> pm + (MultiPool pm,Just pids) -> let + selectedPids = S.fromList pids + in + Map.filterWithKey (\k v -> S.member k selectedPids) pm + + (ResecDeal _,_) -> error "Not implement on liquidate resec deal" + + + + liqAmtByPool = Map.mapWithKey (\k p -> P.pricingPoolFlow d p (pcf Map.! k) lm) poolMapToLiq -- `debug` ("pool id to liq"++ show poolMapToLiq) + liqAmt = sum $ Map.elems liqAmtByPool + + -- Update collected cashflow + newPt = case (pt, mPid) of + (MultiPool pm, Nothing) -> MultiPool $ Map.map liqFunction pm + (MultiPool pm, Just pids) -> let + selectedPids = S.fromList pids + selectedPoolMap = Map.filterWithKey (\k v -> S.member k selectedPids) pm + in + MultiPool $ Map.union (Map.map liqFunction selectedPoolMap) pm + (ResecDeal _,_) -> error "Not implement on liquidate resec deal" + + liqComment = LiquidationProceeds (fromMaybe [] mPid) + accMapAfterLiq = Map.adjust (A.deposit liqAmt d liqComment) an accMap + newPfInRc = foldr (Map.adjust (set (_1 . CF.cashflowTxn) [])) pcf (Map.keys poolMapToLiq) + -- Update current balance to zero + in + return (t {accounts = accMapAfterLiq , pool = newPt} , rc {runPoolFlow = newPfInRc}, logs) performActionWrap d (t, rc, logs) (W.WatchVal ms dss) - = (inspectListVars t d dss) >>= (\vs -> Right (t, rc, DL.snoc logs (InspectWaterfall d ms dss (showInspection <$> vs)))) + = (inspectListVars t rc d dss) >>= (\vs -> Right (t, rc, DL.snoc logs (InspectWaterfall d ms dss (showInspection <$> vs)))) performActionWrap d (t, rc, logs) (W.ActionWithPre p actions) = do - flag <- testPre d t p + flag <- testPre d t rc p if flag then foldM (performActionWrap d) (t,rc,logs) actions else @@ -764,7 +760,7 @@ performActionWrap d (t, rc, logs) (W.ActionWithPre p actions) performActionWrap d (t, rc, logs) (W.ActionWithPre2 p actionsTrue actionsFalse) = do - flag <- testPre d t p + flag <- testPre d t rc p if flag then foldM (performActionWrap d) (t,rc,logs) actionsTrue else @@ -779,7 +775,7 @@ performActionWrap d (t, rc, logs) (W.ChangeStatus mPre newSt) Nothing -> return (t {status=newSt} , rc, DL.snoc logs newLog) Just p -> do - flag <- testPre d t p + flag <- testPre d t rc p if flag then return (t {status=newSt} , rc, DL.snoc logs newLog) else @@ -788,63 +784,63 @@ performActionWrap d (t, rc, logs) (W.ChangeStatus mPre newSt) -- ^ go down to performAction performActionWrap d (t, rc, logs) a = do - dealAfterExe <- performAction d t a + dealAfterExe <- performAction d t rc a return (dealAfterExe, rc, logs) -performAction :: Ast.Asset a => Date -> TestDeal a -> W.Action -> Either ErrorRep (TestDeal a) -performAction d t@TestDeal{accounts=accMap, ledgers = Nothing} (W.TransferAndBook _ _ _ _ _) +performAction :: Ast.Asset a => Date -> TestDeal a -> RunContext -> W.Action -> Either ErrorRep (TestDeal a) +performAction d t@TestDeal{accounts=accMap, ledgers = Nothing} _ (W.TransferAndBook _ _ _ _ _) = Left $ "Date:"++ show d ++" Missing ledger map to book " ++ name t -performAction d t@TestDeal{accounts=accMap, ledgers = Just ledgerM} +performAction d t@TestDeal{accounts=accMap, ledgers = Just ledgerM} rc (W.TransferAndBook mLimit an1 an2 (dr, lName) mComment) = do sourceAcc <- lookupM an1 accMap targetAcc <- lookupM an2 accMap - (transferAmt,accDrawAmt,_) <- calcAvailAfterLimit t d sourceAcc Nothing (A.accBalance sourceAcc) mLimit + (transferAmt,accDrawAmt,_) <- calcAvailAfterLimit t rc d sourceAcc Nothing (A.accBalance sourceAcc) mLimit (sourceAcc', targetAcc') <- A.transfer (sourceAcc,targetAcc) d transferAmt let newLedgerM = Map.adjust (LD.entryLogByDr (dr, transferAmt) d Nothing) lName ledgerM return t {accounts = Map.insert an1 sourceAcc' (Map.insert an2 targetAcc' accMap) , ledgers = Just newLedgerM} -performAction d t@TestDeal{accounts=accMap} (W.Transfer mLimit an1 an2 mComment) +performAction d t@TestDeal{accounts=accMap} rc (W.Transfer mLimit an1 an2 mComment) = do sourceAcc <- lookupM an1 accMap targetAcc <- lookupM an2 accMap - (transferAmt,_,_) <- calcAvailAfterLimit t d sourceAcc Nothing (A.accBalance sourceAcc) mLimit + (transferAmt,_,_) <- calcAvailAfterLimit t rc d sourceAcc Nothing (A.accBalance sourceAcc) mLimit (sourceAcc', targetAcc') <- A.transfer (sourceAcc,targetAcc) d transferAmt return t {accounts = Map.insert an1 sourceAcc' (Map.insert an2 targetAcc' accMap)} -performAction d t@TestDeal{accounts=accMap} (W.TransferMultiple sourceAccList targetAcc mComment) +performAction d t@TestDeal{accounts=accMap} rc (W.TransferMultiple sourceAccList targetAcc mComment) = foldM (\acc (mLimit, sourceAccName) -> - performAction d acc (W.Transfer mLimit sourceAccName targetAcc mComment)) + performAction d acc rc (W.Transfer mLimit sourceAccName targetAcc mComment)) t sourceAccList -- ^ book ledger -performAction d t@TestDeal{ledgers= Nothing} (W.BookBy _) = Left $ "Date:"++ show d ++" Missing ledger map to book " ++ name t -performAction d t@TestDeal{ledgers= Just ledgerM} (W.BookBy (W.Till ledger dr ds)) = +performAction d t@TestDeal{ledgers= Nothing} rc (W.BookBy _) = Left $ "Date:"++ show d ++" Missing ledger map to book " ++ name t +performAction d t@TestDeal{ledgers= Just ledgerM} rc (W.BookBy (W.Till ledger dr ds)) = do - targetAmt <- queryCompound t d ds + targetAmt <- queryCompound t rc d ds ledgerI <- lookupM ledger ledgerM let (bookDirection, amtToBook) = LD.bookToTarget ledgerI (dr, fromRational targetAmt) let newLedgerM = Map.adjust (LD.entryLogByDr (bookDirection,amtToBook) d Nothing) ledger ledgerM return $ t {ledgers = Just newLedgerM } -performAction d t@TestDeal{ledgers= Just ledgerM} (W.BookBy (W.ByDS ledger dr ds)) = +performAction d t@TestDeal{ledgers= Just ledgerM} rc (W.BookBy (W.ByDS ledger dr ds)) = do - amtToBook <- queryCompound t d ds + amtToBook <- queryCompound t rc d ds let newLedgerM = Map.adjust (LD.entryLogByDr (dr,(fromRational amtToBook)) d Nothing) ledger ledgerM return $ t {ledgers = Just newLedgerM } -- ^ it will book ledgers by order with mandatory caps which describes by a -- ^ ds -> value to book -- ^ ledgersList -> list of ledgers to book -performAction d t@TestDeal{ledgers= Just ledgerM} (W.BookBy (W.PDL dr ds ledgersList)) = +performAction d t@TestDeal{ledgers= Just ledgerM} rc (W.BookBy (W.PDL dr ds ledgersList)) = let ledgerNames = fst <$> ledgersList in do - amtToBook <- queryCompound t d ds - ledgCaps <- sequenceA [ queryCompound t d ledgerCap | ledgerCap <- snd <$> ledgersList ] + amtToBook <- queryCompound t rc d ds + ledgCaps <- sequenceA [ queryCompound t rc d ledgerCap | ledgerCap <- snd <$> ledgersList ] let amtBookedToLedgers = paySeqLiabilitiesAmt (fromRational amtToBook) (fromRational <$> ledgCaps) let newLedgerM = foldr (\(ln,amt) acc -> Map.adjust (LD.entryLogByDr (dr,amt) d Nothing) ln acc) @@ -853,7 +849,7 @@ performAction d t@TestDeal{ledgers= Just ledgerM} (W.BookBy (W.PDL dr ds ledgers return $ t {ledgers = Just newLedgerM} -- ^ pay fee sequentially, but not accrued -performAction d t@TestDeal{fees=feeMap, accounts=accMap} (W.PayFeeBySeq mLimit an fns mSupport) = +performAction d t@TestDeal{fees=feeMap, accounts=accMap} rc (W.PayFeeBySeq mLimit an fns mSupport) = let q = DueTotalOf [DueArrears,DueFee] q' = getDueBal d (Just q) @@ -861,15 +857,15 @@ performAction d t@TestDeal{fees=feeMap, accounts=accMap} (W.PayFeeBySeq mLimit a do acc <- lookupM an accMap feesToPay <- lookupVs fns feeMap - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport (sum (map q' feesToPay)) mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport (sum (map q' feesToPay)) mLimit (feesPaid, remainAmt) <- paySeqM d paidOutAmt q' (pay d q) (Right []) feesToPay newAccMap <- adjustM (A.draw d accPaidOut (SeqPayFee fns)) an accMap let dealAfterAcc = t {accounts = newAccMap ,fees = Map.fromList (zip fns feesPaid) <> feeMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -- ^ pay out fee in pro-rata fashion -performAction d t@TestDeal{fees=feeMap, accounts=accMap} (W.PayFee mLimit an fns mSupport) = +performAction d t@TestDeal{fees=feeMap, accounts=accMap} rc (W.PayFee mLimit an fns mSupport) = let q = DueTotalOf [DueArrears,DueFee] qFn = getDueBal d (Just q) @@ -878,20 +874,20 @@ performAction d t@TestDeal{fees=feeMap, accounts=accMap} (W.PayFee mLimit an fns acc <- lookupM an accMap feesToPay <- lookupVs fns feeMap let totalFeeDue = sum $ map qFn feesToPay - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalFeeDue mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalFeeDue mLimit (feesPaid, remainAmt) <- payProM d paidOutAmt qFn (pay d q) feesToPay newAccMap <- adjustM (A.draw d accPaidOut (SeqPayFee fns)) an accMap let dealAfterAcc = t {accounts = newAccMap ,fees = Map.fromList (zip fns feesPaid) <> feeMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t (W.AccrueAndPayIntBySeq mLimit an bnds mSupport) +performAction d t rc (W.AccrueAndPayIntBySeq mLimit an bnds mSupport) = do - dealWithBondDue <- performAction d t (W.CalcBondInt bnds) - performAction d dealWithBondDue (W.PayIntBySeq mLimit an bnds mSupport) + dealWithBondDue <- performAction d t rc (W.CalcBondInt bnds) + performAction d dealWithBondDue rc (W.PayIntBySeq mLimit an bnds mSupport) -performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} +performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} rc (W.PayIntOverIntBySeq mLimit an bnds mSupport) = let q = DueArrears @@ -901,14 +897,14 @@ performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} bndsList <- lookupVs bnds bndMap let dueAmts = qFn <$> bndsList acc <- lookupM an accMap - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport (sum dueAmts) mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport (sum dueAmts) mLimit (bondsPaid,_) <- paySeqM d paidOutAmt qFn (pay d q) (Right []) bndsList newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} +performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} rc (W.PayIntBySeq mLimit an bnds mSupport) = let q = DueTotalOf [DueArrears, DueInterest Nothing] @@ -919,15 +915,15 @@ performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} bndsList <- lookupVs bnds bndMap let dueAmts = qFn <$> bndsList let totalDue = sum dueAmts - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (bondsPaid,_) <- paySeqM d paidOutAmt qFn (pay d q) (Right []) bndsList newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayIntOverInt mLimit an bnds mSupport) = let q = DueArrears @@ -938,15 +934,15 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} bndsList <- lookupVs bnds bndMap let dueAmts = qFn <$> bndsList let totalDue = sum dueAmts - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayInt mLimit an bnds mSupport) = let q = DueTotalOf [DueArrears, DueInterest Nothing] @@ -957,13 +953,13 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} bndsList <- lookupVs bnds bndMap let dueAmts = qFn <$> bndsList let totalDue = sum dueAmts - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t@TestDeal{bonds=bndMap,accounts=accMap,ledgers= Just ledgerM} +performAction d t@TestDeal{bonds=bndMap,accounts=accMap,ledgers= Just ledgerM} rc (W.PayIntAndBook mLimit an bnds mSupport (dr, lName)) = let q = DueTotalOf [DueArrears, DueInterest Nothing] @@ -974,7 +970,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap,ledgers= Just ledgerM} bndsList <- lookupVs bnds bndMap let dueAmts = qFn <$> bndsList let totalDue = sum dueAmts - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList let newLedgerM = Map.adjust (LD.entryLogByDr (dr,paidOutAmt) d Nothing) lName ledgerM newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap @@ -983,24 +979,24 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap,ledgers= Just ledgerM} ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap ,ledgers = Just newLedgerM} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t (W.AccrueAndPayInt mLimit an bnds mSupport) = +performAction d t rc (W.AccrueAndPayInt mLimit an bnds mSupport) = do - dealWithBondDue <- performAction d t (W.CalcBondInt bnds) - performAction d dealWithBondDue (W.PayInt mLimit an bnds mSupport) + dealWithBondDue <- performAction d t rc (W.CalcBondInt bnds) + performAction d dealWithBondDue rc (W.PayInt mLimit an bnds mSupport) -performAction d t (W.CalcAndPayFee mLimit ans fees mSupport) = +performAction d t rc (W.CalcAndPayFee mLimit ans fees mSupport) = do - dealWithFeeDue <- performAction d t (W.CalcFee fees) - performAction d dealWithFeeDue (W.PayFee mLimit ans fees mSupport) + dealWithFeeDue <- performAction d t rc (W.CalcFee fees) + performAction d dealWithFeeDue rc (W.PayFee mLimit ans fees mSupport) -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntResidual mLimit an bndName) = +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayIntResidual mLimit an bndName) = do acc <- lookupM an accMap let availBal = A.accBalance acc - limitAmt <- applyLimit t d availBal availBal mLimit + limitAmt <- applyLimit t rc d availBal availBal mLimit newAccMap <- adjustM (A.draw d limitAmt (PayYield bndName)) an accMap newBondMap <- adjustM (pay d DueResidual limitAmt) bndName bndMap return $ t {accounts = newAccMap , bonds = newBondMap } @@ -1008,7 +1004,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntResidual mLimi -- TODO check for multi interest bond -- TODO support need to patch -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntByRateIndex mLimit an bndNames idx mSupport) +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayIntByRateIndex mLimit an bndNames idx mSupport) = let q = DueInterest (Just idx) qFn = getDueBal d (Just q) @@ -1019,13 +1015,13 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntByRateIndex mL let bndsList = filter (is L._MultiIntBond) $ bndsList' let bndNames_ = L.bndName <$> bndsList let totalDue = sum $ map qFn bndsList - (actualPaidOut,_,_) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (actualPaidOut,_,_) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (paidBonds,_) <- payProM d actualPaidOut qFn (pay d q) bndsList newAccMap <- adjustM (A.draw d actualPaidOut (PayInt bndNames_)) an accMap return $ t {accounts = newAccMap , bonds = Map.fromList (zip bndNames_ paidBonds) <> bndMap} -- TODO support need to patch -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntByRateIndexBySeq mLimit an bndNames idx mSupport) +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayIntByRateIndexBySeq mLimit an bndNames idx mSupport) = let q = DueInterest (Just idx) qFn = getDueBal d (Just q) @@ -1036,22 +1032,22 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntByRateIndexByS let bndNames_ = L.bndName <$> bndsList acc <- lookupM an accMap let totalDue = sum $ map qFn bndsList - (actualPaidOut,_,_) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (actualPaidOut,_,_) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (paidBonds,_) <- paySeqM d actualPaidOut qFn (pay d q) (Right []) bndsList newAccMap <- adjustM (A.draw d actualPaidOut (PayInt bndNames_)) an accMap return $ t {accounts = newAccMap , bonds = Map.fromList (zip bndNames_ paidBonds) <> bndMap} -performAction d t@TestDeal{fees=feeMap,accounts=accMap} (W.PayFeeResidual mlimit an feeName) = +performAction d t@TestDeal{fees=feeMap,accounts=accMap} rc (W.PayFeeResidual mlimit an feeName) = do acc <- lookupM an accMap - paidOutAmt <- applyLimit t d (A.accBalance acc) (A.accBalance acc) mlimit + paidOutAmt <- applyLimit t rc d (A.accBalance acc) (A.accBalance acc) mlimit newAccMap <- adjustM (A.draw d paidOutAmt (PayFeeYield feeName)) an accMap feeMapAfterPay <- adjustM (pay d DueResidual paidOutAmt) feeName feeMap return $ t {accounts = newAccMap, fees = feeMapAfterPay} -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayPrinBySeq mLimit an bnds mSupport) = let q = DuePrincipal @@ -1062,16 +1058,16 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} bndsList <- lookupVs bnds bndMap let bndsToPay = filter (not . L.isPaidOff) bndsList let bndsToPayNames = L.bndName <$> bndsToPay - bndsWithDue <- traverse (calcDuePrin t d) bndsToPay + bndsWithDue <- traverse (calcDuePrin t rc d) bndsToPay let bndsDueAmts = qFn <$> bndsWithDue - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport (sum bndsDueAmts) mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport (sum bndsDueAmts) mLimit (bondsPaid, remainAmt) <- paySeqM d paidOutAmt qFn (pay d q) (Right []) bndsWithDue newAccMap <- adjustM (A.draw d accPaidOut (PayPrin bndsToPayNames)) an accMap let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.fromList (zip bndsToPayNames bondsPaid) <> bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayPrinGroup mLimit an bndGrpName by mSupport) = let bg@(L.BondGroup bndsMap pt) = bndMap Map.! bndGrpName @@ -1079,10 +1075,10 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} in do acc <- lookupM an accMap - bndsWithDueMap <- sequenceA $ Map.map (calcDuePrin t d) bndsMap - bgGap <- queryCompound t d (BondBalanceGapAt d bndGrpName) + bndsWithDueMap <- sequenceA $ Map.map (calcDuePrin t rc d) bndsMap + bgGap <- queryCompound t rc d (BondBalanceGapAt d bndGrpName) let bndsDueAmtsMap = Map.map (\x -> (x, L.bndDuePrin x)) bndsWithDueMap - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport (fromRational bgGap) mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport (fromRational bgGap) mLimit let payOutPlan = allocAmtToBonds by paidOutAmt (Map.elems bndsDueAmtsMap) -- TODO: bond map is not complete bndLstAfterPay <- sequenceA $ map (\(bnd, _amt) -> pay d DuePrincipal _amt bnd) payOutPlan let bndMapAfterPay = (lstToMapByFn L.bndName bndLstAfterPay) <> bndsMap @@ -1090,24 +1086,24 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.insert bndGrpName (L.BondGroup bndMapAfterPay pt) bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -- ^ accure interest and payout interest to a bond group with sequence input "by" -performAction d t@TestDeal{bonds=bndMap} (W.AccrueAndPayIntGroup mLimit an bndName by mSupport) +performAction d t@TestDeal{bonds=bndMap} rc (W.AccrueAndPayIntGroup mLimit an bndName by mSupport) = do - dAfterAcc <- performAction d t (W.AccrueIntGroup [bndName]) - performAction d dAfterAcc (W.PayIntGroup mLimit an bndName by mSupport) + dAfterAcc <- performAction d t rc (W.AccrueIntGroup [bndName]) + performAction d dAfterAcc rc (W.PayIntGroup mLimit an bndName by mSupport) -- ^ accrue interest for a group of bonds -performAction d t@TestDeal{bonds=bndMap} (W.AccrueIntGroup bndNames) +performAction d t@TestDeal{bonds=bndMap} rc (W.AccrueIntGroup bndNames) = do let bondGrp = Map.filterWithKey (\k _ -> S.member k (S.fromList bndNames)) bndMap - bondGrpAccrued <- mapM (calcDueInt t d) bondGrp + bondGrpAccrued <- mapM (calcDueInt t rc d) bondGrp return t {bonds = bondGrpAccrued <> bndMap} -- ^ pay interest for a group of bonds with sequence input "by" -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntGroup mLimit an bndGrpName by mSupport) +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayIntGroup mLimit an bndGrpName by mSupport) = let L.BondGroup bndsMap pt = bndMap Map.! bndGrpName bndsToPayNames = L.bndName <$> Map.elems bndsMap @@ -1116,10 +1112,10 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntGroup mLimit a in do acc <- lookupM an accMap - bndsWithDueMap <- mapM (calcDueInt t d) bndsMap + bndsWithDueMap <- mapM (calcDueInt t rc d) bndsMap let bndsDueAmtsMap = Map.map (\x -> (x, qFn x)) bndsWithDueMap let totalDue = sum $ snd <$> Map.elems bndsDueAmtsMap -- `debug` (">date"++show d++" due amt"++show bndsDueAmtsMap) - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit let payOutPlan = allocAmtToBonds by paidOutAmt (Map.elems bndsDueAmtsMap) -- TODO: bond map is not complete let payOutPlanWithBondName = [ (L.bndName bnd,amt) | (bnd,amt) <- payOutPlan] -- `debug` (">date"++show d++"payOutPlan"++ show payOutPlan) @@ -1131,10 +1127,10 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayIntGroup mLimit a (L.BondGroup ((lstToMapByFn L.bndName bndMapAfterPay) <> bndsMap) pt) bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayPrinWithDue an bnds Nothing) +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayPrinWithDue an bnds Nothing) = let bndsToPay = getActiveBonds t bnds bndsToPayNames = L.bndName <$> bndsToPay @@ -1149,7 +1145,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayPrinWithDue an bn return $ t {accounts = accMapAfterPay, bonds = bndMapUpdated} -performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayPrin mLimit an bnds mSupport) +performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayPrin mLimit an bnds mSupport) = let bndsToPay = getActiveBonds t bnds q = DuePrincipal @@ -1157,20 +1153,20 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} (W.PayPrin mLimit an bn in do acc <- lookupM an accMap - bndsWithDue <- traverse (calcDuePrin t d) bndsToPay + bndsWithDue <- traverse (calcDuePrin t rc d) bndsToPay let bndsDueAmts = qFn <$> bndsWithDue let bndsToPayNames = L.bndName <$> bndsWithDue let totalDue = sum bndsDueAmts - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t d acc mSupport totalDue mLimit + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (bondsPaid, remainAmt) <- payProM d paidOutAmt qFn (pay d q) bndsWithDue newAccMap <- adjustM (A.draw d accPaidOut (PayPrin bndsToPayNames)) an accMap let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.fromList (zip bndsToPayNames bondsPaid) <> bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc + updateSupport d mSupport supportPaidOut dealAfterAcc rc -- ^ pay principal without any limit -performAction d t@TestDeal{accounts=accMap, bonds=bndMap} (W.PayPrinResidual an bnds) = +performAction d t@TestDeal{accounts=accMap, bonds=bndMap} rc (W.PayPrinResidual an bnds) = let bndsToPay = [ b { L.bndDuePrin = L.bndBalance b} | b <- getActiveBonds t bnds ] bndsToPayNames = L.bndName <$> bndsToPay @@ -1186,10 +1182,10 @@ performAction d t@TestDeal{accounts=accMap, bonds=bndMap} (W.PayPrinResidual an accMapAfterPay <- adjustM (A.draw d actualPaidOut (PayPrin bnds)) an accMap return $ t {accounts = accMapAfterPay, bonds = bndMapUpdated} -- `debug` ("Bond Prin Pay Result"++show(bndMapUpdated)) -performAction d t@TestDeal{accounts=accMap, bonds=bndMap} (W.FundWith mlimit an bnd) = +performAction d t@TestDeal{accounts=accMap, bonds=bndMap} rc (W.FundWith mlimit an bnd) = do fundAmt_ <- case mlimit of - Just (DS ds) -> queryCompound t d (patchDateToStats d ds) + Just (DS ds) -> queryCompound t rc d (patchDateToStats d ds) Just (DueCapAmt amt) -> return $ toRational amt _ -> Left $ "Date:"++show d ++"Not valid limit for funding with bond"++ show bnd let fundAmt = fromRational fundAmt_ @@ -1199,21 +1195,21 @@ performAction d t@TestDeal{accounts=accMap, bonds=bndMap} (W.FundWith mlimit an return $ t {accounts = accMapAfterFund, bonds= Map.fromList [(bnd,bndFunded)] <> bndMap } -- ^ write off bonds and book -performAction d t@TestDeal{bonds = bndMap, ledgers = Just ledgerM } +performAction d t@TestDeal{bonds = bndMap, ledgers = Just ledgerM } rc (W.WriteOffAndBook mLimit bnd (dr,lName)) = do bndToWriteOff <- lookupM bnd bndMap let bndBal = L.bndBalance bndToWriteOff - writeAmt <- applyLimit t d bndBal bndBal mLimit + writeAmt <- applyLimit t rc d bndBal bndBal mLimit let newLedgerM = Map.adjust (LD.entryLogByDr (dr,writeAmt) d (Just (WriteOff bnd writeAmt))) lName ledgerM bndWritedOff <- writeOff d DuePrincipal writeAmt bndToWriteOff return $ t {bonds = Map.fromList [(bnd,bndWritedOff)] <> bndMap, ledgers = Just newLedgerM} -performAction d t@TestDeal{bonds=bndMap} (W.WriteOff mlimit bnd) +performAction d t@TestDeal{bonds=bndMap} rc (W.WriteOff mlimit bnd) = do bndToWriteOff <- lookupM bnd bndMap writeAmt <- case mlimit of - Just (DS ds) -> queryCompound t d (patchDateToStats d ds) + Just (DS ds) -> queryCompound t rc d (patchDateToStats d ds) Just (DueCapAmt amt) -> return $ toRational amt Nothing -> return $ toRational . L.bndBalance $ bndToWriteOff x -> Left $ "Date:"++show d ++"not supported type to determine the amount to write off"++ show x @@ -1222,49 +1218,49 @@ performAction d t@TestDeal{bonds=bndMap} (W.WriteOff mlimit bnd) bndWritedOff <- writeOff d DuePrincipal writeAmtCapped $ bndToWriteOff return $ t {bonds = Map.fromList [(bnd,bndWritedOff)] <> bndMap} -performAction d t@TestDeal{bonds=bndMap, ledgers = Just ledgerM} +performAction d t@TestDeal{bonds=bndMap, ledgers = Just ledgerM} rc (W.WriteOffBySeqAndBook mLimit bnds (dr,lName)) = do - bndsToWriteOff <- mapM (calcDueInt t d . (bndMap Map.!)) bnds + bndsToWriteOff <- mapM (calcDueInt t rc d . (bndMap Map.!)) bnds let totalBondBal = sum $ L.bndBalance <$> bndsToWriteOff -- total amount to be write off - writeAmt <- applyLimit t d totalBondBal totalBondBal mLimit + writeAmt <- applyLimit t rc d totalBondBal totalBondBal mLimit (bndWrited, _) <- paySeqM d writeAmt L.bndBalance (writeOff d DuePrincipal) (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} -performAction d t@TestDeal{bonds=bndMap } (W.WriteOffBySeq mLimit bnds) +performAction d t@TestDeal{bonds=bndMap } rc (W.WriteOffBySeq mLimit bnds) = do - bondsToWriteOff <- mapM (calcDueInt t d . (bndMap Map.!)) bnds + bondsToWriteOff <- mapM (calcDueInt t rc d . (bndMap Map.!)) bnds let totalBondBal = sum $ L.bndBalance <$> bondsToWriteOff - writeAmt <- applyLimit t d totalBondBal totalBondBal mLimit + writeAmt <- applyLimit t rc d totalBondBal totalBondBal mLimit (bndWrited, _) <- paySeqM d writeAmt L.bndBalance (writeOff d DuePrincipal) (Right []) bondsToWriteOff let bndMapUpdated = lstToMapByFn L.bndName bndWrited return t {bonds = bndMapUpdated <> bndMap } -performAction d t@TestDeal{fees=feeMap} (W.CalcFee fns) +performAction d t@TestDeal{fees=feeMap} rc (W.CalcFee fns) = do - newFeeMap <- mapM (calcDueFee t d) $ getFeeByName t (Just fns) + newFeeMap <- mapM (calcDueFee t rc d) $ getFeeByName t (Just fns) return t {fees = newFeeMap <> feeMap } -- TODO need to check bond names exists -- TODO wont' persert the bond shape for a bond group -performAction d t@TestDeal{bonds=bndMap} (W.CalcBondInt bns) +performAction d t@TestDeal{bonds=bndMap} rc (W.CalcBondInt bns) = do - newBondMap <- mapM (calcDueInt t d) $ getBondsByName t (Just bns) + newBondMap <- mapM (calcDueInt t rc d) $ getBondsByName t (Just bns) return t {bonds = newBondMap <> bndMap} -- ^ set due prin mannually -performAction d t@TestDeal{bonds=bndMap} (W.CalcBondPrin2 mLimit bnds) +performAction d t@TestDeal{bonds=bndMap} rc (W.CalcBondPrin2 mLimit bnds) = do bndsToPay <- lookupVs bnds bndMap let bndsToPayNames = L.bndName <$> bndsToPay - bndsDueAmts <- traverse (L.bndDuePrin <$>) $ (calcDuePrin t d) <$> bndsToPay + bndsDueAmts <- traverse (L.bndDuePrin <$>) $ (calcDuePrin t rc d) <$> bndsToPay let totalDue = sum bndsDueAmts - bookCap <- applyLimit t d totalDue totalDue mLimit + bookCap <- applyLimit t rc d totalDue totalDue mLimit let bndsAmountToBook = zip bndsToPayNames $ prorataFactors bndsDueAmts bookCap let newBndMap = foldr (\(bn,amt) acc -> Map.adjust (\b -> b {L.bndDuePrin = amt}) bn acc) @@ -1272,14 +1268,14 @@ performAction d t@TestDeal{bonds=bndMap} (W.CalcBondPrin2 mLimit bnds) bndsAmountToBook -- `debug` ("Calc Bond Prin"++ show bndsAmountToBePaid) return $ t {bonds = newBndMap} -performAction d t@TestDeal{bonds=bndMap, accounts = accMap} (W.CalcBondPrin mLimit accName bnds mSupport) +performAction d t@TestDeal{bonds=bndMap, accounts = accMap} rc (W.CalcBondPrin mLimit accName bnds mSupport) = do bndsToPay <- lookupVs bnds bndMap let bndsToPayNames = L.bndName <$> bndsToPay acc <- lookupM accName accMap let accBal = A.accBalance acc - bndsDueAmts <- traverse (L.bndDuePrin <$>) $ (calcDuePrin t d) <$> bndsToPay - (payAmount,_,_) <- calcAvailAfterLimit t d acc mSupport (sum bndsDueAmts) mLimit + bndsDueAmts <- traverse (L.bndDuePrin <$>) $ (calcDuePrin t rc d) <$> bndsToPay + (payAmount,_,_) <- calcAvailAfterLimit t rc d acc mSupport (sum bndsDueAmts) mLimit let bndsAmountToBePaid = zip bndsToPayNames $ prorataFactors bndsDueAmts payAmount let newBndMap = foldr (\(bn,amt) acc -> Map.adjust (\b -> b {L.bndDuePrin = amt}) bn acc) @@ -1289,9 +1285,9 @@ performAction d t@TestDeal{bonds=bndMap, accounts = accMap} (W.CalcBondPrin mLim -- ^ draw cash and deposit to account -performAction d t@TestDeal{accounts=accs, liqProvider = Nothing} (W.LiqSupport mLimit pName CE.LiqToAcc ans) +performAction d t@TestDeal{accounts=accs, liqProvider = Nothing} rc (W.LiqSupport mLimit pName CE.LiqToAcc ans) = Left $ "Date:"++show d++"Can't support account as no liq provider defined in deal"++ show (name t) -performAction d t@TestDeal{accounts=accs, liqProvider = Just _liqProvider} (W.LiqSupport mLimit pName CE.LiqToAcc ans) +performAction d t@TestDeal{accounts=accs, liqProvider = Just _liqProvider} rc (W.LiqSupport mLimit pName CE.LiqToAcc ans) | length ans == 1 = let liq = _liqProvider Map.! pName @@ -1301,8 +1297,8 @@ performAction d t@TestDeal{accounts=accs, liqProvider = Just _liqProvider} (W.Li transferAmt <- case (CE.liqCredit liq, mLimit) of (Unlimit, Nothing) -> Left $ "Date:"++show d ++"Can't deposit unlimit cash to an account in LiqSupport(Account):"++ show pName ++ ":"++ show an (ByAvailAmount av, Nothing) -> Right . toRational $ av - (Unlimit, Just (DS ds)) -> queryCompound t d (patchDateToStats d ds) -- `debug` ("hit with ds"++ show ds) - (ByAvailAmount av, Just (DS ds)) -> (min (toRational av)) <$> queryCompound t d (patchDateToStats d ds) + (Unlimit, Just (DS ds)) -> queryCompound t rc d (patchDateToStats d ds) -- `debug` ("hit with ds"++ show ds) + (ByAvailAmount av, Just (DS ds)) -> (min (toRational av)) <$> queryCompound t rc d (patchDateToStats d ds) (_ , Just _x) -> Left $ "Date:"++show d ++"Not support limit in LiqSupport(Account)"++ show _x let dAmt = fromRational transferAmt newLiqMap <- adjustM (draw d dAmt LiquidationDraw) pName _liqProvider @@ -1328,14 +1324,14 @@ performAction d t@TestDeal{accounts=accs, liqProvider = Just _liqProvider} (W.Li -- TODO : add pay int by sequence -- TODO : may not work for bond group -performAction d t@TestDeal{bonds=bndMap,liqProvider = Nothing} (W.LiqSupport mLimit pName CE.LiqToBondInt bns) +performAction d t@TestDeal{bonds=bndMap,liqProvider = Nothing} rc (W.LiqSupport mLimit pName CE.LiqToBondInt bns) = Left $ "Date:"++show d++"Can't support bond interest as no liq provider defined in deal"++ show (name t) -performAction d t@TestDeal{bonds=bndMap,liqProvider = Just _liqProvider} +performAction d t@TestDeal{bonds=bndMap,liqProvider = Just _liqProvider} rc (W.LiqSupport mLimit pName CE.LiqToBondInt bns) = do liq <- lookupM pName _liqProvider - totalDueInt <- queryCompound t d (CurrentDueBondInt bns) - supportAmt <- applyLimit t d (fromRational totalDueInt) (fromRational totalDueInt) mLimit + totalDueInt <- queryCompound t rc d (CurrentDueBondInt bns) + supportAmt <- applyLimit t rc d (fromRational totalDueInt) (fromRational totalDueInt) mLimit let transferAmt = case CE.liqCredit liq of Unlimit -> supportAmt @@ -1348,9 +1344,9 @@ performAction d t@TestDeal{bonds=bndMap,liqProvider = Just _liqProvider} -- ^ payout due interest / due fee / oustanding balance to liq provider -performAction d t@TestDeal{accounts=accs,liqProvider = Nothing} (W.LiqRepay mLimit rpt an pName) +performAction d t@TestDeal{accounts=accs,liqProvider = Nothing} rc (W.LiqRepay mLimit rpt an pName) = Left $ "Date:"++show d++"Can't repay to liq provider as no liq provider defined in deal"++ show (name t) -performAction d t@TestDeal{accounts=accs,liqProvider = Just _liqProvider} (W.LiqRepay mLimit rpt an pName) +performAction d t@TestDeal{accounts=accs,liqProvider = Just _liqProvider} rc (W.LiqRepay mLimit rpt an pName) = let liqDueAmts CE.LiqBal = [ CE.liqBalance $ _liqProvider Map.! pName] @@ -1359,8 +1355,8 @@ performAction d t@TestDeal{accounts=accs,liqProvider = Just _liqProvider} (W.Liq liqDueAmts (CE.LiqRepayTypes lrts) = concat $ liqDueAmts <$> lrts overDrawnBalance = case (CE.liqCredit $ _liqProvider Map.! pName) of - Unlimit -> 0 - ByAvailAmount v -> negate v + Unlimit -> 0 + ByAvailAmount v -> negate v dueBreakdown | overDrawnBalance > 0 = overDrawnBalance:liqDueAmts rpt @@ -1371,7 +1367,7 @@ performAction d t@TestDeal{accounts=accs,liqProvider = Just _liqProvider} (W.Liq cap = min liqTotalDues $ A.accBalance $ accs Map.! an in do - transferAmt <- applyLimit t d cap cap mLimit + transferAmt <- applyLimit t rc d cap cap mLimit let paidOutsToLiq = paySeqLiabilitiesAmt transferAmt dueBreakdown let rptsToPair = case rpt of @@ -1390,36 +1386,36 @@ performAction d t@TestDeal{accounts=accs,liqProvider = Just _liqProvider} (W.Liq return $ t { accounts = newAccMap, liqProvider = Just newLiqMap } -- ^ pay yield to liq provider -performAction d t@TestDeal{accounts=accs,liqProvider = Nothing } (W.LiqYield limit an pName) +performAction d t@TestDeal{accounts=accs,liqProvider = Nothing } rc (W.LiqYield limit an pName) = Left $ "Date:"++show d++"Can't pay yield to liq provider as no liq provider defined in deal"++ show (name t) -performAction d t@TestDeal{accounts=accs,liqProvider = Just _liqProvider} (W.LiqYield limit an pName) +performAction d t@TestDeal{accounts=accs,liqProvider = Just _liqProvider} rc (W.LiqYield limit an pName) = do acc <- lookupM an accs let cap = A.accBalance acc transferAmt <- case limit of Nothing -> return (toRational cap) - Just (DS ds) -> (min (toRational cap)) <$> (queryCompound t d (patchDateToStats d ds)) + Just (DS ds) -> (min (toRational cap)) <$> (queryCompound t rc d (patchDateToStats d ds)) _ -> Left $ "Date:"++show d ++"Not implement the limit"++ show limit++"For Pay Yield to liqProvider" newAccMap <- adjustM (A.draw d (fromRational transferAmt) (LiquidationSupport pName)) an accs let newLiqMap = Map.adjust (CE.repay (fromRational transferAmt) d CE.LiqResidual) pName _liqProvider return t { accounts = newAccMap, liqProvider = Just newLiqMap } -performAction d t@TestDeal{liqProvider = Nothing} (W.LiqAccrue liqNames) +performAction d t@TestDeal{liqProvider = Nothing} rc (W.LiqAccrue liqNames) = Left $ "Date:"++show d++"Can't accrue liq provider as no liq provider defined in deal"++ show (name t) -performAction d t@TestDeal{liqProvider = Just _liqProvider} (W.LiqAccrue liqNames) +performAction d t@TestDeal{liqProvider = Just _liqProvider} rc (W.LiqAccrue liqNames) = let - updatedLiqProvider = mapWithinMap ((updateLiqProvider t d) . (CE.accrueLiqProvider d)) liqNames _liqProvider + updatedLiqProvider = mapWithinMap ((updateLiqProvider t rc d) . (CE.accrueLiqProvider d)) liqNames _liqProvider in return $ t {liqProvider = Just updatedLiqProvider} -performAction d t@TestDeal{rateSwap = Nothing } (W.SwapAccrue sName) +performAction d t@TestDeal{rateSwap = Nothing } rc (W.SwapAccrue sName) = Left $ "Date:"++show d++"Can't accrue swap at location:"++ show sName++"as no swap defined in deal"++ show (name t) -performAction d t@TestDeal{rateSwap = Just rtSwap } (W.SwapAccrue sName) +performAction d t@TestDeal{rateSwap = Just rtSwap } rc (W.SwapAccrue sName) = do refBal <- case HE.rsNotional (rtSwap Map.! sName) of (HE.Fixed b) -> return b - (HE.Base ds) -> fromRational <$> queryCompound t d (patchDateToStats d ds) + (HE.Base ds) -> fromRational <$> queryCompound t rc d (patchDateToStats d ds) (HE.Schedule ts) -> Right . fromRational $ getValByDate ts Inc d let newRtSwap = Map.adjust @@ -1429,9 +1425,9 @@ performAction d t@TestDeal{rateSwap = Just rtSwap } (W.SwapAccrue sName) return $ t { rateSwap = Just newRtSwap } -performAction d t@TestDeal{rateCap = Nothing, accounts = accsMap } (W.CollectRateCap accName sName) +performAction d t@TestDeal{rateCap = Nothing, accounts = accsMap } rc (W.CollectRateCap accName sName) = Left $ "Date:"++show d++"Can't collect rate cap at location:"++ show sName++"as no rate cap defined in deal"++ show (name t) -performAction d t@TestDeal{rateCap = Just rcM, accounts = accsMap } (W.CollectRateCap accName sName) +performAction d t@TestDeal{rateCap = Just rcM, accounts = accsMap } rc (W.CollectRateCap accName sName) = let receiveAmt = max 0 $ HE.rcNetCash $ rcM Map.! sName newRcSwap = Map.adjust (HE.receiveRC d) sName rcM -- `debug` ("REceiv AMT"++ show receiveAmt) @@ -1442,9 +1438,9 @@ performAction d t@TestDeal{rateCap = Just rcM, accounts = accsMap } (W.CollectRa -- TODO lookup check is not necessiary -performAction d t@TestDeal{rateSwap = Nothing, accounts = accsMap } (W.SwapReceive accName sName) +performAction d t@TestDeal{rateSwap = Nothing, accounts = accsMap } rc (W.SwapReceive accName sName) = Left $ "Date:"++show d++"Can't receive swap at location:"++ show sName++"as no swap defined in deal"++ show (name t) -performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } (W.SwapReceive accName sName) +performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } rc (W.SwapReceive accName sName) = do acc <- lookupM accName accsMap rSwap <- lookupM sName rtSwap @@ -1456,9 +1452,9 @@ performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } (W.SwapR in t { rateSwap = Just newRtSwap, accounts = newAccMap } -performAction d t@TestDeal{rateSwap = Nothing, accounts = accsMap } (W.SwapPay accName sName) +performAction d t@TestDeal{rateSwap = Nothing, accounts = accsMap } rc (W.SwapPay accName sName) = Left $ "Date:"++show d++"Can't pay swap at location:"++ show sName++"as no swap defined in deal"++ show (name t) -performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } (W.SwapPay accName sName) +performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } rc (W.SwapPay accName sName) = do acc <- lookupM accName accsMap rSwap <- lookupM sName rtSwap @@ -1476,20 +1472,20 @@ performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } (W.SwapP return t -performAction d t@TestDeal{rateSwap = Nothing, accounts = accsMap } (W.SwapSettle accName sName) +performAction d t@TestDeal{rateSwap = Nothing, accounts = accsMap } rc (W.SwapSettle accName sName) = Left $ "Date:"++show d++"Can't settle swap at location:"++ show sName++"as no swap defined in deal"++ show (name t) -performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } (W.SwapSettle accName sName) +performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } rc (W.SwapSettle accName sName) = do - t2 <- performAction d t (W.SwapReceive accName sName) - performAction d t2 (W.SwapPay accName sName) + t2 <- performAction d t rc (W.SwapReceive accName sName) + performAction d t2 rc (W.SwapPay accName sName) -performAction d t@TestDeal{ triggers = Nothing } (W.RunTrigger loc tNames) +performAction d t@TestDeal{ triggers = Nothing } rc (W.RunTrigger loc tNames) = Left $ "Date:"++show d++"Can't run trigger at location:"++ show loc++"as no trigger defined in deal"++ show (name t) -performAction d t@TestDeal{ triggers = Just trgM } (W.RunTrigger loc tNames) +performAction d t@TestDeal{ triggers = Just trgM } rc (W.RunTrigger loc tNames) = do triggerM <- lookupM loc trgM triggerList <- lookupVs tNames triggerM - tList <- mapM (testTrigger t d) triggerList + tList <- mapM (testTrigger t rc d) triggerList return $ let newTrgMap = Map.fromList $ zip tNames tList @@ -1497,6 +1493,6 @@ performAction d t@TestDeal{ triggers = Just trgM } (W.RunTrigger loc tNames) t { triggers = Just (Map.insert loc newTrgMap trgM) } -performAction d t (W.Placeholder mComment) = return t +performAction d t rc (W.Placeholder mComment) = return t -performAction d t action = Left $ "failed to match action>>"++show action++">>Deal"++show (name t) +performAction d t rc action = Left $ "failed to match action>>"++show action++">>Deal"++show (name t) diff --git a/src/Deal/DealBase.hs b/src/Deal/DealBase.hs index cd4635bc..8aba7788 100644 --- a/src/Deal/DealBase.hs +++ b/src/Deal/DealBase.hs @@ -16,6 +16,7 @@ module Deal.DealBase (TestDeal(..),SPV(..),dealBonds,dealFees,dealAccounts,dealP ,DealStatFields(..),getDealStatInt,isPreClosing,populateDealDates ,bondTraversal,findBondByNames,updateBondInMap,traverseBondMap,traverseBondMapByFn ,_MultiPool,_ResecDeal,uDealFutureCf,uDealFutureScheduleCf + ,RunContext(..) ) where import qualified Accounts as A @@ -206,6 +207,15 @@ type PoolCollectionActions = [ActionOnDate] type BondDistributionActions = [ActionOnDate] type CustomActions = [ActionOnDate] +data RunContext = RunContext{ + runPoolFlow:: Map.Map PoolId CF.PoolCashflow + ,revolvingAssump:: Maybe (Map.Map String (RevolvingPool ,AP.ApplyAssumptionType)) + ,revolvingInterestRateAssump:: Maybe [RateAssumption] + } + deriving (Show) + + + populateDealDates :: DateDesp -> DealStatus -> Either ErrorRep (Date,Date,Date,PoolCollectionActions,BondDistributionActions,Date,CustomActions) populateDealDates (PreClosingDates cutoff closing mRevolving end (firstCollect,poolDp) (firstPay,bondDp)) _ = let diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index c9023a8c..b888e1b0 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -54,18 +54,18 @@ import qualified Cashflow as P debug = flip trace -- | calcuate target balance for a reserve account, 0 for a non-reserve account -calcTargetAmount :: P.Asset a => TestDeal a -> Date -> A.Account -> Either ErrorRep Balance -calcTargetAmount t d (A.Account _ _ _ Nothing _ ) = return 0 -calcTargetAmount t d (A.Account _ _ _ (Just r) _ ) = +calcTargetAmount :: P.Asset a => TestDeal a -> RunContext -> Date -> A.Account -> Either ErrorRep Balance +calcTargetAmount t rc d (A.Account _ _ _ Nothing _ ) = return 0 +calcTargetAmount t rc d (A.Account _ _ _ (Just r) _ ) = let eval :: A.ReserveAmount -> Either ErrorRep Balance eval ra = case ra of A.PctReserve ds _rate -> do - v <- queryCompound t d (patchDateToStats d ds) + v <- queryCompound t rc d (patchDateToStats d ds) return (fromRational (v * _rate)) A.FixReserve amt -> return amt A.Either p ra1 ra2 -> do - q <- testPre d t p + q <- testPre d t rc p if q then eval ra1 else @@ -76,27 +76,27 @@ calcTargetAmount t d (A.Account _ _ _ (Just r) _ ) = eval r -- | calculate target bond balance for a bond -calcBondTargetBalance :: P.Asset a => TestDeal a -> Date -> L.Bond -> Either ErrorRep Balance -calcBondTargetBalance t d (L.BondGroup bMap mPt) = +calcBondTargetBalance :: P.Asset a => TestDeal a -> RunContext -> Date -> L.Bond -> Either ErrorRep Balance +calcBondTargetBalance t rc d (L.BondGroup bMap mPt) = case mPt of Nothing -> do - vs <- traverse (calcBondTargetBalance t d) $ Map.elems bMap + vs <- traverse (calcBondTargetBalance t rc d) $ Map.elems bMap return $ sum vs Just (L.PAC _target) -> return $ getValOnByDate _target d Just (L.PacAnchor _target _bnds) - | queryDealBool t (IsPaidOff _bnds) d == Right True -> + | queryDealBool t rc (IsPaidOff _bnds) d == Right True -> do - subBondTargets <- traverse (calcBondTargetBalance t d) $ Map.elems bMap + subBondTargets <- traverse (calcBondTargetBalance t rc d) $ Map.elems bMap return $ sum subBondTargets - | queryDealBool t (IsPaidOff _bnds) d == Right False -> return $ getValOnByDate _target d + | queryDealBool t rc (IsPaidOff _bnds) d == Right False -> return $ getValOnByDate _target d | otherwise -> Left $ "Calculate paid off bonds failed"++ show _bnds ++" in calc target balance" Just (L.AmtByPeriod pc) -> case getValFromPerCurve pc Past Inc (fromMaybe 0 (getDealStatInt t BondPaidPeriod)) of Just v -> return v Nothing -> Left "Failed to find value in calcTargetBalance" _ -> Left $ "not support principal type for bond group"++ show mPt -calcBondTargetBalance t d b = +calcBondTargetBalance t rc d b = case L.bndType b of L.Sequential -> return 0 L.Lockout ld @@ -109,8 +109,8 @@ calcBondTargetBalance t d b = L.Equity -> return 0 L.PAC _target -> Right $ getValOnByDate _target d L.PacAnchor _target _bnds - | queryDealBool t (IsPaidOff _bnds) d == Right True -> return 0 - | queryDealBool t (IsPaidOff _bnds) d == Right False -> return $ getValOnByDate _target d + | queryDealBool t rc (IsPaidOff _bnds) d == Right True -> return 0 + | queryDealBool t rc (IsPaidOff _bnds) d == Right False -> return $ getValOnByDate _target d | otherwise -> Left $ "Calculate paid off bonds failed"++ show _bnds ++" in calc target balance" L.AmtByPeriod pc -> case getValFromPerCurve pc Past Inc (fromMaybe 0 (getDealStatInt t BondPaidPeriod)) of Just v -> return v @@ -199,8 +199,6 @@ poolSourceToIssuanceField NewDelinquencies = HistoryDelinquency poolSourceToIssuanceField a = error ("Failed to match pool source when mapping to issuance field"++show a) - - eval :: (P.Asset a, Num b) => TestDeal a -> Date -> EvalExpr b -> Either ErrorRep b eval t d exp = let @@ -212,57 +210,57 @@ eval t d exp = -- (EvalSubtract x:xs) -> (eval' x) - liftA sum $ sequenceA (eval' <$> xs) -- eval (EvalAvg xs) = (sum (eval <$> xs)) / (length xs) -queryCompound :: P.Asset a => TestDeal a -> Date -> DealStats -> Either ErrorRep Rational +queryCompound :: P.Asset a => TestDeal a -> RunContext -> Date -> DealStats -> Either ErrorRep Rational queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=feeMap, pool=pt} + rc@RunContext{runPoolFlow=outstandingFlow} d s = case s of - Sum _s -> sum <$> sequenceA [ queryCompound t d __s | __s <- _s ] - Substract dss -> queryCompound t d (Subtract dss) + Sum _s -> sum <$> sequenceA [ queryCompound t rc d __s | __s <- _s ] + Substract dss -> queryCompound t rc d (Subtract dss) Subtract [] -> Left $ "Date:"++show d++"Can not subtract empty list" Subtract (ds:dss) -> do - a <- queryCompound t d ds - bs <- queryCompound t d (Sum dss) + a <- queryCompound t rc d ds + bs <- queryCompound t rc d (Sum dss) return $ a - bs Avg [] -> Left $ "Date:"++show d++"Can not average empty list" - Avg dss -> (/ (toRational (length dss))) <$> (sum <$> sequenceA (queryCompound t d <$> dss )) - Max ss -> maximum' [ queryCompound t d s | s <- ss ] - Min ss -> minimum' [ queryCompound t d s | s <- ss ] - Divide ds1 ds2 -> if (queryCompound t d ds2) == Right 0 then + Avg dss -> (/ (toRational (length dss))) <$> (sum <$> sequenceA (queryCompound t rc d <$> dss )) + Max ss -> maximum' [ queryCompound t rc d s | s <- ss ] + Min ss -> minimum' [ queryCompound t rc d s | s <- ss ] + Divide ds1 ds2 -> if (queryCompound t rc d ds2) == Right 0 then Left $ "Date:"++show d++"Can not divide zero on ds: "++ show ds2 else - liftA2 (/) (queryCompound t d ds1) (queryCompound t d ds2) - Factor s f -> (* f) <$> queryCompound t d s - FloorAndCap floor cap s -> max (queryCompound t d floor) $ min (queryCompound t d cap) (queryCompound t d s) - Multiply ss -> product <$> sequenceA [ queryCompound t d _s | _s <- ss] - FloorWith s floor -> liftA2 max (queryCompound t d s) (queryCompound t d floor) - FloorWithZero s -> max 0 <$> queryCompound t d s + liftA2 (/) (queryCompound t rc d ds1) (queryCompound t rc d ds2) + Factor s f -> (* f) <$> queryCompound t rc d s + FloorAndCap floor cap s -> max (queryCompound t rc d floor) $ min (queryCompound t rc d cap) (queryCompound t rc d s) + Multiply ss -> product <$> sequenceA [ queryCompound t rc d _s | _s <- ss] + FloorWith s floor -> liftA2 max (queryCompound t rc d s) (queryCompound t rc d floor) + FloorWithZero s -> max 0 <$> queryCompound t rc d s Excess (s1:ss) -> do - q1 <- queryCompound t d s1 - q2 <- queryCompound t d (Sum ss) -- `debug` ("Excess"++show (queryCompound t s1)++"ss"++show ( queryCompound t (Sum ss))) + q1 <- queryCompound t rc d s1 + q2 <- queryCompound t rc d (Sum ss) -- `debug` ("Excess"++show (queryCompound t s1)++"ss"++show ( queryCompound t (Sum ss))) return (max 0 (q1-q2)) - CapWith s cap -> min (queryCompound t d s) (queryCompound t d cap) - Abs s -> abs <$> queryCompound t d s + CapWith s cap -> min (queryCompound t rc d s) (queryCompound t rc d cap) + Abs s -> abs <$> queryCompound t rc d s Round ds rb -> do - q <- queryCompound t d ds + q <- queryCompound t rc d ds return $ roundingBy rb q - DivideRatio s1 s2 -> queryCompound t d (Divide s1 s2) - AvgRatio ss -> queryCompound t d (Avg ss) + DivideRatio s1 s2 -> queryCompound t rc d (Divide s1 s2) + AvgRatio ss -> queryCompound t rc d (Avg ss) Constant v -> return v -- rate query - BondFactor -> queryCompound t d (Divide CurrentBondBalance OriginalBondBalance) + BondFactor -> queryCompound t rc d (Divide CurrentBondBalance OriginalBondBalance) BondFactorOf bn -> - queryCompound t d (Divide (CurrentBondBalanceOf [bn]) (OriginalBondBalanceOf [bn])) - PoolFactor mPns -> - queryCompound t d (Divide (CurrentPoolBalance mPns) (OriginalPoolBalance mPns)) + queryCompound t rc d (Divide (CurrentBondBalanceOf [bn]) (OriginalBondBalanceOf [bn])) + PoolFactor mPns -> queryCompound t rc d (Divide (CurrentPoolBalance mPns) (OriginalPoolBalance mPns)) FutureCurrentPoolFactor asOfDay mPns -> - queryCompound t d (Divide (FutureCurrentPoolBalance mPns) (OriginalPoolBalance mPns)) + queryCompound t rc d (Divide (FutureCurrentPoolBalance mPns) (OriginalPoolBalance mPns)) CumulativePoolDefaultedRate mPns -> - queryCompound t d (Divide (PoolCumCollection [NewDefaults] mPns) (OriginalPoolBalance mPns)) + queryCompound t rc d (Divide (PoolCumCollection [NewDefaults] mPns) (OriginalPoolBalance mPns)) CumulativeNetLossRatio mPns -> - queryCompound t d (Divide (CumulativeNetLoss mPns) (OriginalPoolBalance mPns)) + queryCompound t rc d (Divide (CumulativeNetLoss mPns) (OriginalPoolBalance mPns)) CumulativePoolDefaultedRateTill idx mPns -> - queryCompound t d (Divide (PoolCumCollectionTill idx [NewDefaults] mPns) (OriginalPoolBalance mPns)) + queryCompound t rc d (Divide (PoolCumCollectionTill idx [NewDefaults] mPns) (OriginalPoolBalance mPns)) BondRate bn -> case Map.lookup bn (bonds t) of @@ -275,8 +273,8 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f BondWaRate bns -> do - rs <- sequenceA $ (\bn -> queryCompound t d (BondRate bn)) <$> bns - ws <- sequenceA $ (\bn -> queryCompound t d (CurrentBondBalanceOf [bn])) <$> bns + rs <- sequenceA $ (\bn -> queryCompound t rc d (BondRate bn)) <$> bns + ws <- sequenceA $ (\bn -> queryCompound t rc d (CurrentBondBalanceOf [bn])) <$> bns return $ weightedBy (fromRational <$> ws) rs PoolWaRate Nothing -> @@ -350,14 +348,14 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f ReserveBalance ans -> do - accBal <- lookupAndApplies (calcTargetAmount t d) ("Date:"++show d++"Cal Reserve Balance") ans accMap + accBal <- lookupAndApplies (calcTargetAmount t rc d) ("Date:"++show d++"Cal Reserve Balance") ans accMap vs <- sequenceA accBal return $ toRational (sum vs) - ReserveExcessAt _d ans -> queryCompound t d (Excess [AccBalance ans, ReserveBalance ans]) + ReserveExcessAt _d ans -> queryCompound t rc d (Excess [AccBalance ans, ReserveBalance ans]) - ReserveGapAt _d ans -> queryCompound t d (Excess [ReserveBalance ans, AccBalance ans]) + ReserveGapAt _d ans -> queryCompound t rc d (Excess [ReserveBalance ans, AccBalance ans]) -- CurrentBondBalance -> Right . toRational $ Map.foldr (\x acc -> getCurBalance x + acc) 0.0 bndMap CurrentBondBalance -> return $ sum $ toRational . getCurBalance <$> Map.elems bndMap @@ -422,7 +420,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f FutureCurrentPoolBalance mPns -> case (mPns,pt) of - (Nothing, MultiPool pm ) -> queryCompound t d (FutureCurrentPoolBalance (Just $ Map.keys pm)) + (Nothing, MultiPool pm ) -> queryCompound t rc d (FutureCurrentPoolBalance (Just $ Map.keys pm)) (Just pids, MultiPool pm) -> if S.isSubsetOf (S.fromList pids) (S.fromList (Map.keys pm)) then let @@ -466,8 +464,8 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f CumulativeNetLoss mPns -> liftA2 (-) - (queryCompound t d (CumulativePoolDefaultedBalance mPns)) - (queryCompound t d (CumulativePoolRecoveriesBalance mPns)) + (queryCompound t rc d (CumulativePoolDefaultedBalance mPns)) + (queryCompound t rc d (CumulativePoolRecoveriesBalance mPns)) PoolCumCollection ps mPns -> let @@ -514,7 +512,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f do curPoolBalM <- sequenceA $ Map.mapWithKey - (\k v -> queryCompound t d (FutureCurrentPoolBalance (Just [k]))) + (\k v -> queryCompound t rc d (FutureCurrentPoolBalance (Just [k]))) pStat -- `debug` ("date"++show d++"Pool stats collection: "++ show pStat) let poolStat = Map.mapWithKey (\k v -> @@ -539,8 +537,8 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f txnsRates = CF.mflowRate <$> txns in do - scheduleBal <- queryCompound t d (FutureCurrentSchedulePoolBegBalance mPns) - curBal <- queryCompound t d (FutureCurrentPoolBalance mPns) + scheduleBal <- queryCompound t rc d (FutureCurrentSchedulePoolBegBalance mPns) + curBal <- queryCompound t rc d (FutureCurrentPoolBalance mPns) let factor = case scheduleBal of 0.00 -> 0 _ -> curBal / scheduleBal -- `debug` ("cur Bal"++show curBal ++">> sheduleBal"++ show scheduleBal) @@ -636,12 +634,12 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f Nothing -> Right . toRational $ sum [ (snd . LD.ledgBalance) lg | lg <- lgs ] BondBalanceGapAt d bName -> - queryCompound t d (Excess [CurrentBondBalanceOf [bName], BondBalanceTarget [bName]]) + queryCompound t rc d (Excess [CurrentBondBalanceOf [bName], BondBalanceTarget [bName]]) BondBalanceTarget bNames -> do bnds <- findBondByNames bndMap bNames - targets <- sequenceA $ calcBondTargetBalance t d <$> bnds + targets <- sequenceA $ calcBondTargetBalance t rc d <$> bnds return $ toRational $ sum targets FeesPaidAt d fns -> @@ -664,7 +662,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f -- ^ get total due (due int + int over int due) for bonds CurrentDueBondIntTotal bns -> - sum <$> sequenceA (queryCompound t d <$> [CurrentDueBondInt bns,CurrentDueBondIntOverInt bns]) + sum <$> sequenceA (queryCompound t rc d <$> [CurrentDueBondInt bns,CurrentDueBondIntOverInt bns]) CurrentDueBondIntAt idx bns -> let @@ -685,7 +683,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f Just dueInts -> Right . toRational $ sum $ dueInts CurrentDueBondIntTotalAt idx bns -> - sum <$> sequenceA (queryCompound t d <$> [CurrentDueBondIntAt idx bns,CurrentDueBondIntOverIntAt idx bns]) + sum <$> sequenceA (queryCompound t rc d <$> [CurrentDueBondIntAt idx bns,CurrentDueBondIntOverIntAt idx bns]) CurrentDueFee fns -> do @@ -819,7 +817,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f case Map.lookup s mCustom of Just (CustomConstant v) -> Right . toRational $ v Just (CustomCurve cv) -> Right . toRational $ getValOnByDate cv d - Just (CustomDS ds) -> queryCompound t d (patchDateToStats d ds ) + Just (CustomDS ds) -> queryCompound t rc d (patchDateToStats d ds ) _ -> Left $ "Date:"++show d++"Unsupported custom data found for key " ++ show s DealStatBalance s -> @@ -833,9 +831,10 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f -queryDealBool :: P.Asset a => TestDeal a -> DealStats -> Date -> Either ErrorRep Bool +queryDealBool :: P.Asset a => TestDeal a -> RunContext -> DealStats -> Date -> Either ErrorRep Bool queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap , liqProvider = liqProviderMap, rateSwap = rateCapMap } + rc ds d = case ds of @@ -889,7 +888,7 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap return $ and rps TestRate ds cmp _r -> do - testRate <- queryCompound t d ds + testRate <- queryCompound t rc d ds let r = toRational r return $ case cmp of G -> testRate > r @@ -901,7 +900,7 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap HasPassedMaturity bns -> do bMap <- selectInMap "Bond Pass Maturity" bns bndMap let oustandingBnds = Map.filter (not . isPaidOff) bMap - ms <- sequenceA $ (\bn -> queryCompound t d (MonthsTillMaturity bn)) <$> L.bndName <$> oustandingBnds + ms <- sequenceA $ (\bn -> queryCompound t rc d (MonthsTillMaturity bn)) <$> L.bndName <$> oustandingBnds return $ all (<= 0) ms IsDealStatus st -> return $ status t == st @@ -915,40 +914,40 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap - TestNot ds -> do not <$> (queryDealBool t ds d) - TestAny b dss -> anyM (\ x -> (== b) <$> queryDealBool t x d ) dss - TestAll b dss -> allM (\ x -> (== b) <$> queryDealBool t x d ) dss + TestNot ds -> do not <$> (queryDealBool t rc ds d) + TestAny b dss -> anyM (\ x -> (== b) <$> queryDealBool t rc x d ) dss + TestAll b dss -> allM (\ x -> (== b) <$> queryDealBool t rc x d ) dss _ -> Left ("Date:"++show d++"Failed to query bool type formula"++ show ds) -- ^ test a condition with a deal and a date -testPre :: P.Asset a => Date -> TestDeal a -> Pre -> Either String Bool -testPre d t p = +testPre :: P.Asset a => Date -> TestDeal a -> RunContext -> Pre -> Either String Bool +testPre d t rc p = case p of - Types.All pds -> allM (testPre d t) pds - -- Types.Any pds -> return $ any (testPre d t) pds - Types.Any pds -> anyM (testPre d t) pds + Types.All pds -> allM (testPre d t rc) pds + -- Types.Any pds -> return $ any (testPre d t rc) pds + Types.Any pds -> anyM (testPre d t rc) pds IfZero s -> do - q <- queryCompound t d s + q <- queryCompound t rc d s return $ (round q) == 0 If cmp s amt -> do - q <- (queryCompound t d (ps s)) + q <- (queryCompound t rc d (ps s)) return $ toCmp cmp q (toRational amt) -- `debug` (show d++"if cmp "++show (queryDeal t (ps s))++"amt"++show amt) IfRate cmp s amt -> do - q <- (queryCompound t d (ps s)) + q <- (queryCompound t rc d (ps s)) return $ toCmp cmp q (toRational amt) -- `debug` (show d++"rate"++show (queryDealRate t (ps s))++"amt"++show amt) IfInt cmp s amt -> do - q <- (queryCompound t d (ps s)) + q <- (queryCompound t rc d (ps s)) return $ toCmp cmp q (toRational amt) -- Integer test IfIntIn s iset -> do - q <- (queryCompound t d (ps s)) + q <- (queryCompound t rc d (ps s)) return $ (round q) `elem` iset IfIntBetween s rt i1 i2 -> do - v <- queryCompound t d (ps s) + v <- queryCompound t rc d (ps s) case rt of II -> return $ (round v) >= i1 && (round v) <= i2 IE -> return $ (round v) >= i1 && (round v) < i2 @@ -963,14 +962,14 @@ testPre d t p = IfDateIn ds -> return $ d `elem` ds IfCurve cmp s _ts -> do - q <- (queryCompound t d (ps s)) + q <- (queryCompound t rc d (ps s)) return $ toCmp cmp q (getValByDate _ts Inc d) - IfRateCurve cmp s _ts -> do v <- (queryCompound t d (ps s)) + IfRateCurve cmp s _ts -> do v <- (queryCompound t rc d (ps s)) return $ (toCmp cmp) v (getValByDate _ts Inc d) IfByPeriodCurve cmp sVal sSelect pc -> do - v <- queryCompound t d (ps sVal) - selector <- queryCompound t d (ps sSelect) + v <- queryCompound t rc d (ps sVal) + selector <- queryCompound t rc d (ps sSelect) case getValFromPerCurve pc Past Inc (round $ fromRational selector) of Nothing -> Left $ "Date:"++show d++"Failed to find value from period curve"++ show pc Just vFromCurve -> @@ -978,33 +977,33 @@ testPre d t p = IfRateByPeriodCurve cmp sVal sSelect pc -> do - v <- queryCompound t d (ps sVal) - selector <- queryCompound t d (ps sSelect) + v <- queryCompound t rc d (ps sVal) + selector <- queryCompound t rc d (ps sSelect) case getValFromPerCurve pc Past Inc (round $ fromRational selector) of Nothing -> Left $ "Date:"++show d++"Failed to find value from period curve"++ show pc Just vFromCurve -> return $ (toCmp cmp) (fromRational v) vFromCurve - IfBool s True -> queryDealBool t s d + IfBool s True -> queryDealBool t rc s d IfBool s False -> do - q <- (queryDealBool t s d) + q <- (queryDealBool t rc s d) return q If2 cmp s1 s2 -> do - q1 <- (queryCompound t d (ps s1)) - q2 <- (queryCompound t d (ps s2)) + q1 <- (queryCompound t rc d (ps s1)) + q2 <- (queryCompound t rc d (ps s2)) return (toCmp cmp q1 q2) IfRate2 cmp s1 s2 -> do - q1 <- (queryCompound t d (ps s1)) - q2 <- (queryCompound t d (ps s2)) + q1 <- (queryCompound t rc d (ps s1)) + q2 <- (queryCompound t rc d (ps s2)) return (toCmp cmp q1 q2) IfInt2 cmp s1 s2 -> do - q1 <- (queryCompound t d (ps s1)) - q2 <- (queryCompound t d (ps s2)) + q1 <- (queryCompound t rc d (ps s1)) + q2 <- (queryCompound t rc d (ps s2)) return (toCmp cmp q1 q2) IfDealStatus st -> return $ status t == st -- `debug` ("current date"++show d++">> stutus"++show (status t )++"=="++show st) Always b -> return b - IfNot _p -> not <$> testPre d t _p + IfNot _p -> not <$> testPre d t rc _p where toCmp x = case x of G -> (>) @@ -1015,25 +1014,25 @@ testPre d t p = ps = patchDateToStats d -- ^ convert a condition to string in a deal context -preToStr :: P.Asset a => TestDeal a -> Date -> Pre -> String -preToStr t d p = +preToStr :: P.Asset a => TestDeal a -> RunContext -> Date -> Pre -> String +preToStr t rc d p = case p of - (IfZero ds) -> "0 == " ++ show (fromRational <$> (queryCompound t d (ps ds))) - (If cmp ds bal) -> show (fromRational <$> (queryCompound t d (ps ds))) ++" "++ show cmp ++" " ++show bal - (IfRate cmp ds r) -> show (fromRational <$> (queryCompound t d (ps ds))) ++" "++ show cmp ++" " ++show r - (IfInt cmp ds r) -> show (fromRational <$> (queryCompound t d (ps ds))) ++" "++ show cmp ++" " ++show r - (IfCurve cmp ds ts) -> show (fromRational <$> (queryCompound t d (ps ds))) ++" "++ show cmp ++" " ++show (fromRational (getValByDate ts Inc d)) + (IfZero ds) -> "0 == " ++ show (fromRational <$> (queryCompound t rc d (ps ds))) + (If cmp ds bal) -> show (fromRational <$> (queryCompound t rc d (ps ds))) ++" "++ show cmp ++" " ++show bal + (IfRate cmp ds r) -> show (fromRational <$> (queryCompound t rc d (ps ds))) ++" "++ show cmp ++" " ++show r + (IfInt cmp ds r) -> show (fromRational <$> (queryCompound t rc d (ps ds))) ++" "++ show cmp ++" " ++show r + (IfCurve cmp ds ts) -> show (fromRational <$> (queryCompound t rc d (ps ds))) ++" "++ show cmp ++" " ++show (fromRational (getValByDate ts Inc d)) (IfDate cmp _d) -> show d ++" "++ show cmp ++" " ++show _d - (IfBool ds b) -> show (fromRational <$> (queryCompound t d ds)) ++" == "++ show b - (If2 cmp ds1 ds2) -> show (fromRational <$> (queryCompound t d (ps ds1))) ++" "++ show cmp ++" " ++show (fromRational <$> (queryCompound t d (ps ds2))) - (IfRate2 cmp ds1 ds2) -> show (fromRational <$> (queryCompound t d (ps ds1))) ++" "++ show cmp ++" " ++show (fromRational <$> (queryCompound t d (ps ds2))) - (IfInt2 cmp ds1 ds2) -> show (fromRational <$> (queryCompound t d (ps ds1))) ++" "++ show cmp ++" " ++show (fromRational <$> (queryCompound t d (ps ds2))) + (IfBool ds b) -> show (fromRational <$> (queryCompound t rc d ds)) ++" == "++ show b + (If2 cmp ds1 ds2) -> show (fromRational <$> (queryCompound t rc d (ps ds1))) ++" "++ show cmp ++" " ++show (fromRational <$> (queryCompound t rc d (ps ds2))) + (IfRate2 cmp ds1 ds2) -> show (fromRational <$> (queryCompound t rc d (ps ds1))) ++" "++ show cmp ++" " ++show (fromRational <$> (queryCompound t rc d (ps ds2))) + (IfInt2 cmp ds1 ds2) -> show (fromRational <$> (queryCompound t rc d (ps ds1))) ++" "++ show cmp ++" " ++show (fromRational <$> (queryCompound t rc d (ps ds2))) (IfDealStatus st) -> show (status t) ++" == "++ show st (IfByPeriodCurve cmp ds1 ds2 pc) -> let - v = (fromRational <$> queryCompound t d (ps ds1)) + v = (fromRational <$> queryCompound t rc d (ps ds1)) in - case (fromRational <$> queryCompound t d (ps ds2)) of + case (fromRational <$> queryCompound t rc d (ps ds2)) of Left _error -> "Failed to read selector for period curve"++ show ds2 ++ "Error:"++ _error Right s -> let @@ -1042,9 +1041,9 @@ preToStr t d p = show v ++" "++ show cmp ++" " ++show c (IfRateByPeriodCurve cmp ds1 ds2 pc) -> let - v = (fromRational <$> queryCompound t d (ps ds1)) + v = (fromRational <$> queryCompound t rc d (ps ds1)) in - case queryCompound t d (ps ds2) of + case queryCompound t rc d (ps ds2) of Left _error -> "Failed to read selector for period curve"++ show ds2 ++ "Error:"++ _error Right s -> let @@ -1052,13 +1051,13 @@ preToStr t d p = in show v ++" "++ show cmp ++" " ++show (fromRational <$> c) (Always b) -> show b - (IfNot _p) -> "Not "++ preToStr t d _p - (Types.All pds) -> "All:["++ intercalate "|" (map (preToStr t d) pds)++"]" - (Types.Any pds) -> "Any:["++ intercalate "|" (map (preToStr t d) pds)++"]" + (IfNot _p) -> "Not "++ preToStr t rc d _p + (Types.All pds) -> "All:["++ intercalate "|" (map (preToStr t rc d) pds)++"]" + (Types.Any pds) -> "Any:["++ intercalate "|" (map (preToStr t rc d) pds)++"]" _ -> "Failed to read condition"++ show p where ps = patchDateToStats d -testPre2 :: P.Asset a => Date -> TestDeal a -> Pre -> (String, Either ErrorRep Bool) -testPre2 d t p = (preToStr t d p, testPre d t p) +testPre2 :: P.Asset a => Date -> TestDeal a -> RunContext -> Pre -> (String, Either ErrorRep Bool) +testPre2 d t rc p = (preToStr t rc d p, testPre d t rc p) \ No newline at end of file diff --git a/src/Deal/DealRun.hs b/src/Deal/DealRun.hs index bfb7acfa..d728bb97 100644 --- a/src/Deal/DealRun.hs +++ b/src/Deal/DealRun.hs @@ -52,7 +52,7 @@ runEffects (t@TestDeal{accounts = accMap, fees = feeMap ,status=st, bonds = bond = case te of DealStatusTo _ds -> return (t {status = _ds}, rc, actions, logs) DoAccrueFee fns -> do - newFeeList <- traverse (calcDueFee t d) $ (feeMap Map.!) <$> fns + newFeeList <- traverse (calcDueFee t rc d) $ (feeMap Map.!) <$> fns let newFeeMap = Map.fromList (zip fns newFeeList) <> feeMap return (t {fees = newFeeMap}, rc, actions, logs) @@ -86,7 +86,7 @@ runEffects (t@TestDeal{accounts = accMap, fees = feeMap ,status=st, bonds = bond DoNothing -> return (t, rc, actions, DL.empty) _ -> Left $ "Date:"++ show d++" Failed to match trigger effects: "++show te -setBondStepUpRate :: Date -> [RateAssumption] -> L.Bond -> Either String L.Bond +setBondStepUpRate :: Date -> [RateAssumption] -> L.Bond -> Either ErrorRep L.Bond setBondStepUpRate d ras b@(L.Bond _ _ _ ii (Just sp) _ _ _ _ _ _ _ _ _) = return $ let @@ -109,56 +109,56 @@ setBondStepUpRate d ras bg@(L.BondGroup bMap pt) return $ L.BondGroup m pt -- ^ update bond interest rate from rate assumption -setBondNewRate :: Ast.Asset a => TestDeal a -> Date -> [RateAssumption] -> L.Bond -> Either String L.Bond -setBondNewRate t d ras b@(L.Bond _ _ L.OriginalInfo{ L.originDate = od} ii _ bal currentRate _ dueInt _ Nothing _ _ _) - = setBondNewRate t d ras b {L.bndDueIntDate = Just od} +setBondNewRate :: Ast.Asset a => TestDeal a -> RunContext -> Date -> [RateAssumption] -> L.Bond -> Either ErrorRep L.Bond +setBondNewRate t rc d ras b@(L.Bond _ _ L.OriginalInfo{ L.originDate = od} ii _ bal currentRate _ dueInt _ Nothing _ _ _) + = setBondNewRate t rc d ras b {L.bndDueIntDate = Just od} -- ^ Floater rate -setBondNewRate t d ras b@(L.Bond _ _ _ ii@(L.Floater br idx _spd rset dc mf mc) _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) +setBondNewRate t rc d ras b@(L.Bond _ _ _ ii@(L.Floater br idx _spd rset dc mf mc) _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) = return $ (L.accrueInt d b){ L.bndRate = AP.applyFloatRate ii d ras } -- ^ Fix rate, do nothing -setBondNewRate t d ras b@(L.Bond _ _ _ L.Fix {} _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) +setBondNewRate t rc d ras b@(L.Bond _ _ _ L.Fix {} _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) = return b -- ^ Ref rate -setBondNewRate t d ras b@(L.Bond _ _ _ (L.RefRate sr ds factor _) _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) +setBondNewRate t rc d ras b@(L.Bond _ _ _ (L.RefRate sr ds factor _) _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) = do let b' = L.accrueInt d b - rate <- queryCompound t d (patchDateToStats d ds) + rate <- queryCompound t rc d (patchDateToStats d ds) return b' {L.bndRate = fromRational (rate * toRational factor) } -- ^ cap & floor & IoI -setBondNewRate t d ras b@(L.Bond _ _ _ ii _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) +setBondNewRate t rc d ras b@(L.Bond _ _ _ ii _ bal currentRate _ dueInt _ (Just dueIntDate) _ _ _) = return $ (L.accrueInt d b) { L.bndRate = AP.applyFloatRate ii d ras} -- ^ bond group -setBondNewRate t d ras bg@(L.BondGroup bMap pt) +setBondNewRate t rc d ras bg@(L.BondGroup bMap pt) = do - m <- mapM (setBondNewRate t d ras) bMap + m <- mapM (setBondNewRate t rc d ras) bMap return $ L.BondGroup m pt -- ^ apply all rates for multi-int bond -setBondNewRate t d ras b@(L.MultiIntBond bn _ _ iis _ bal currentRates _ dueInts dueIoIs _ _ _ _) +setBondNewRate t rc d ras b@(L.MultiIntBond bn _ _ iis _ bal currentRates _ dueInts dueIoIs _ _ _ _) = let newRates = AP.applyFloatRate <$> iis <*> pure d <*> pure ras b' = L.accrueInt d b -- `debug` ("accrue due to new rate "++ bn) in return $ b' { L.bndRates = newRates } -updateRateSwapBal :: Ast.Asset a => TestDeal a -> Date -> HE.RateSwap -> Either String HE.RateSwap -updateRateSwapBal t d rs@HE.RateSwap{ HE.rsNotional = base } +updateRateSwapBal :: Ast.Asset a => TestDeal a -> RunContext -> Date -> HE.RateSwap -> Either String HE.RateSwap +updateRateSwapBal t rc d rs@HE.RateSwap{ HE.rsNotional = base } = case base of HE.Fixed _ -> return rs HE.Schedule ts -> return $ rs { HE.rsRefBalance = fromRational (getValByDate ts Inc d) } HE.Base ds -> do - v <- queryCompound t d (patchDateToStats d ds) + v <- queryCompound t rc d (patchDateToStats d ds) return rs { HE.rsRefBalance = fromRational v} -- `debug` ("query Result"++ show (patchDateToStats d ds) ) -updateRateSwapRate :: Ast.Asset a => TestDeal a -> Maybe [RateAssumption] -> Date -> HE.RateSwap -> Either String HE.RateSwap -updateRateSwapRate t Nothing _ _ = Left "Failed to update rate swap: No rate input assumption" -updateRateSwapRate t (Just rAssumps) d rs@HE.RateSwap{ HE.rsType = rt } +updateRateSwapRate :: Ast.Asset a => TestDeal a -> RunContext -> Maybe [RateAssumption] -> Date -> HE.RateSwap -> Either String HE.RateSwap +updateRateSwapRate t _ Nothing _ _ = Left "Failed to update rate swap: No rate input assumption" +updateRateSwapRate t rc (Just rAssumps) d rs@HE.RateSwap{ HE.rsType = rt } = let getRate x = AP.lookupRate rAssumps x d in @@ -179,13 +179,13 @@ updateRateSwapRate t (Just rAssumps) d rs@HE.RateSwap{ HE.rsType = rt } return (r, _r) HE.FormulaToFloating ds flter -> do - _r <- queryCompound t d (patchDateToStats d ds) + _r <- queryCompound t rc d (patchDateToStats d ds) r <- getRate flter return (fromRational _r, r) HE.FloatingToFormula flter ds -> do r <- getRate flter - _r <- queryCompound t d (patchDateToStats d ds) + _r <- queryCompound t rc d (patchDateToStats d ds) return (r, fromRational _r) return rs {HE.rsPayingRate = pRate, HE.rsReceivingRate = rRate } @@ -207,7 +207,7 @@ runTriggers (t@TestDeal{status=oldStatus, triggers = Just trgM},rc, actions) d d let trgsToTest = Map.filter (\trg -> (not (trgStatus trg) || trgStatus trg && trgCurable trg)) trgsMap - triggeredTrgs <- mapM (testTrigger t d) trgsToTest + triggeredTrgs <- mapM (testTrigger t rc d) trgsToTest let triggeredEffects = [ trgEffects _trg | _trg <- Map.elems triggeredTrgs, (trgStatus _trg) ] (newDeal, newRc, newActions, logsFromTrigger) <- foldM (`runEffects` d) (t,rc,actions, DL.empty) triggeredEffects let newStatus = status newDeal @@ -296,7 +296,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= ,waterfall=waterfallM,name=dealName,pool=pt,stats=_stat} rc@(RunContext poolFlowMap rAssump rates) (Just (ad:ads)) calls log - | futureCashToCollectFlag && (queryCompound t (getDate ad) AllAccBalance == Right 0) && (dStatus /= Revolving) && (dStatus /= Warehousing Nothing) --TODO need to use prsim here to cover all warehouse status + | futureCashToCollectFlag && (queryCompound t rc (getDate ad) AllAccBalance == Right 0) && (dStatus /= Revolving) && (dStatus /= Warehousing Nothing) --TODO need to use prsim here to cover all warehouse status = let endingLog = EndRun (Just (getDate ad)) "No Pool Cashflow/All Account is zero/Not revolving" endingDate = getDate ad @@ -348,12 +348,12 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= AccruePoolCollection d x -> do - t' <- (accrueDeal d (fromMaybe [] rates) t) + t' <- (accrueDeal d (fromMaybe [] rates) t rc) run t' rc (Just (PoolCollection d x:ads)) calls log AccrueRunWaterfall d x -> do - t' <- (accrueDeal d (fromMaybe [] rates) t) + t' <- (accrueDeal d (fromMaybe [] rates) t rc) run t' rc (Just (RunWaterfall d x:ads)) calls log RunWaterfall d "" -> @@ -373,7 +373,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= = DL.snoc newLogs0 (WarningMsg (" No waterfall distribution found on date "++show d++" with waterfall key "++show waterfallKey++"from"++ (show (Map.keys waterfallM)))) | otherwise = newLogs0 - flag <- anyM (testPre d dRunWithTrigger0) callTest + flag <- anyM (testPre d dRunWithTrigger0 rc1) callTest if flag then -- Clean Up Waterfall Actions do @@ -381,7 +381,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= | null cleanUpActions = [DealStatusChangeTo d dStatus Called "Call by triggers before waterfall distribution"] | otherwise = [DealStatusChangeTo d dStatus Called "Call by triggers before waterfall distribution", RunningWaterfall d W.CleanUp] (dealAfterCleanUp, rc_, newLogWaterfall_) <- foldM (performActionWrap d) (dRunWithTrigger0, rc1,log) cleanUpActions - endingLogs <- Rpt.patchFinancialReports dealAfterCleanUp d newLogWaterfall_ + endingLogs <- Rpt.patchFinancialReports dealAfterCleanUp rc_ d newLogWaterfall_ return (dealAfterCleanUp , DL.concat [logsBeforeDist,endingLogs,DL.fromList (newStLogs++[EndRun (Just d) "Clean Up"])] , runPoolFlow rc_ @@ -419,7 +419,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= AccrueFee d feeName -> do fToAcc <- maybeToEither ("Failed to find fee "++feeName) (Map.lookup feeName feeMap) - newF <- calcDueFee t d fToAcc + newF <- calcDueFee t rc d fToAcc let newFeeMap = Map.fromList [(feeName,newF)] <> feeMap run (t{fees=newFeeMap}) rc (Just ads) calls log @@ -428,7 +428,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= Nothing -> run t rc (Just ads) calls log (Just mLiqProvider) -> let -- update credit - newLiqMap = Map.adjust (updateLiqProvider t d) liqName mLiqProvider + newLiqMap = Map.adjust (updateLiqProvider t rc d) liqName mLiqProvider in run (t{liqProvider = Just newLiqMap}) rc (Just ads) calls log ResetLiqProviderRate d liqName -> @@ -461,8 +461,8 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= Nothing -> Left $ " No rate swaps modeled when looking for "++ sn Just rSwap -> do - newRateSwap_rate <- adjustM (updateRateSwapRate t rates d) sn rSwap - newRateSwap_bal <- adjustM (updateRateSwapBal t d) sn newRateSwap_rate + newRateSwap_rate <- adjustM (updateRateSwapRate t rc rates d) sn rSwap + newRateSwap_bal <- adjustM (updateRateSwapBal t rc d) sn newRateSwap_rate let newRateSwap_acc = Map.adjust (HE.accrueIRS d) sn newRateSwap_bal run (t{rateSwap = Just newRateSwap_acc}) rc (Just ads) calls log @@ -506,13 +506,13 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= _rates = fromMaybe [] rates in do - newRateCap <- adjustM (accrueRC t d _rates) cn rCap + newRateCap <- adjustM (accrueRC t rc d _rates) cn rCap run (t{rateCap = Just newRateCap}) rc (Just ads) calls log InspectDS d dss -> do - newlog <- inspectListVars t d dss - run t rc (Just ads) calls log + newlog <- inspectListVars t rc d dss + run t rc (Just ads) calls $ DL.append log (DL.fromList newlog) ResetBondRate d bn -> let @@ -520,7 +520,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= bnd = bndMap Map.! bn in do - newBnd <- setBondNewRate t d rateList bnd + newBnd <- setBondNewRate t rc d rateList bnd run t{bonds = Map.fromList [(bn,newBnd)] <> bndMap} rc (Just ads) calls log StepUpBondRate d bn -> @@ -547,7 +547,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= cashReport = Rpt.buildCashReport t sd ed in do - bsReport <- Rpt.buildBalanceSheet t ed + bsReport <- Rpt.buildBalanceSheet t rc ed let newlog = FinancialReport sd ed bsReport cashReport run t rc (Just ads) calls log -- `debug` ("new log"++ show ed++ show newlog) FireTrigger d cyc n -> @@ -573,8 +573,8 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= do factor <- liftA2 (/) - (queryCompound t d (FutureCurrentPoolBegBalance Nothing)) - (queryCompound t d (FutureCurrentSchedulePoolBegBalance Nothing)) + (queryCompound t rc d (FutureCurrentPoolBegBalance Nothing)) + (queryCompound t rc d (FutureCurrentSchedulePoolBegBalance Nothing)) let reduceCfs = Map.map (\f -> (over CF.cashflowTxn (\xs -> CF.scaleTsRow factor <$> xs) f, Nothing ) ) schedulePoolFlowMap -- need to apply with factor and trucate with date (runDealWithSchedule,_,_) <- run t (RunContext reduceCfs rAssump rates) (Just ads) calls log let bondWal = Map.map (L.calcWalBond d) (bonds runDealWithSchedule) -- `debug` ("Bond schedule flow"++ show (bonds runDealWithSchedule)) @@ -612,7 +612,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 - flag <- testPre d t p + flag <- testPre d t rc p case flag of False -> run t rc (Just ads) calls log True -> @@ -627,7 +627,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= IssueBond d (Just p) bGroupName accName bnd mBal mRate -> do - flag <- testPre d t p + flag <- testPre d t rc p case flag of False -> run t rc (Just ads) calls log True -> let @@ -635,10 +635,10 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= in do newBalance <- case mBal of - Just _q -> queryCompound t d (patchDateToStats d _q) + Just _q -> queryCompound t rc d (patchDateToStats d _q) Nothing -> Right . toRational $ L.originBalance (L.bndOriginInfo bnd) newRate <- case mRate of - Just _q -> queryCompound t d (patchDateToStats d _q) + Just _q -> queryCompound t rc d (patchDateToStats d _q) Nothing -> return $ L.originRate (L.bndOriginInfo bnd) let newBonds = case Map.lookup bGroupName bndMap of Nothing -> bndMap @@ -671,7 +671,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= newRate = L.getBeginRate iInfo in do - nBnd <- calcDueInt t d $ bndMap Map.! bName + nBnd <- calcDueInt t rc d $ bndMap Map.! bName let dueIntToPay = L.getTotalDueInt nBnd let acc = accMap Map.! accName let actualPayout = min (A.accBalance acc) dueIntToPay @@ -691,7 +691,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= timeBasedTests::[Pre] = snd (fromMaybe ([],[]) calls) in do - flags::[Bool] <- traverse (testPre d t) timeBasedTests + flags::[Bool] <- traverse (testPre d t rc) timeBasedTests case any id flags of True -> let @@ -701,7 +701,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= in do (dealAfterCleanUp, rc_, newLogWaterfall_ ) <- foldM (performActionWrap d) (t, rc, log) cleanUpActions - endingLogs <- Rpt.patchFinancialReports dealAfterCleanUp d newLogWaterfall_ + endingLogs <- Rpt.patchFinancialReports dealAfterCleanUp rc_ d newLogWaterfall_ return (dealAfterCleanUp , DL.snoc (endingLogs `DL.append` newStLogs) (EndRun (Just d) "Clean Up") , (runPoolFlow rc_)) @@ -709,7 +709,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= StopRunTest d pres -> do - flags::[Bool] <- sequenceA $ [ (testPre d t pre) | pre <- pres ] + flags::[Bool] <- sequenceA $ [ (testPre d t rc pre) | pre <- pres ] case all id flags of True -> return (t, DL.snoc log (EndRun (Just d) ("Stop Run Test by:"++ show (zip pres flags))), poolFlowMap) _ -> run t rc (Just ads) calls log diff --git a/src/Reports.hs b/src/Reports.hs index 972de836..b4fc9d96 100644 --- a/src/Reports.hs +++ b/src/Reports.hs @@ -21,7 +21,7 @@ import qualified Liability as L import Control.Applicative (liftA3) import Types import Deal.DealBase - ( TestDeal(TestDeal, pool, fees, bonds, accounts,liqProvider,rateSwap), getIssuanceStatsConsol ,poolTypePool, dealPool) + ( TestDeal(TestDeal, pool, fees, bonds, accounts,liqProvider,rateSwap), getIssuanceStatsConsol ,poolTypePool, dealPool,RunContext) import Deal.DealQuery ( queryCompound ) import Deal.DealAction ( calcDueFee, calcDueInt ) import Data.Maybe (fromMaybe) @@ -37,9 +37,9 @@ import Stmt FlowDirection(Outflow, Inflow) ) -- ^ add financial report to the logs -patchFinancialReports :: P.Asset a => TestDeal a -> Date -> DL.DList ResultComponent -> Either String (DL.DList ResultComponent) +patchFinancialReports :: P.Asset a => TestDeal a -> RunContext-> Date -> DL.DList ResultComponent -> Either String (DL.DList ResultComponent) -- patchFinancialReports t d DL.empty = return (DL.empty) -patchFinancialReports t d logs +patchFinancialReports t rc d logs = case (find pickReportLog (reverse (DL.toList logs))) of Nothing -> Right logs Just (FinancialReport sd ed bs cash) @@ -47,7 +47,7 @@ patchFinancialReports t d logs cashReport = buildCashReport t ed d in do - bsReport <- buildBalanceSheet t d + bsReport <- buildBalanceSheet t rc d let newlog = FinancialReport ed d bsReport cashReport return (DL.snoc logs newlog) where @@ -58,12 +58,12 @@ getItemBalance :: BookItem -> Balance getItemBalance (Item _ bal) = bal getItemBalance (ParentItem _ items) = sum $ getItemBalance <$> items -getPoolBalanceStats :: P.Asset a => TestDeal a -> Date -> Maybe [PoolId] -> Either String [Balance] -getPoolBalanceStats t d mPid +getPoolBalanceStats :: P.Asset a => TestDeal a -> RunContext -> Date -> Maybe [PoolId] -> Either String [Balance] +getPoolBalanceStats t rc d mPid = let - poolStats = [queryCompound t d (FutureCurrentPoolBalance mPid) - ,(queryCompound t d (PoolCumCollection [NewDefaults] mPid)) - ,negate <$> (queryCompound t d (PoolCumCollection [CollectedRecoveries] mPid))] + poolStats = [queryCompound t rc d (FutureCurrentPoolBalance mPid) + ,(queryCompound t rc d (PoolCumCollection [NewDefaults] mPid)) + ,negate <$> (queryCompound t rc d (PoolCumCollection [CollectedRecoveries] mPid))] in do poolStats2::[Rational] <- sequenceA poolStats @@ -71,8 +71,9 @@ getPoolBalanceStats t d mPid type PoolBalanceSnapshot = (Balance, Balance, Balance) -buildBalanceSheet :: P.Asset a => TestDeal a -> Date -> Either String BalanceSheetReport +buildBalanceSheet :: P.Asset a => TestDeal a -> RunContext-> Date -> Either String BalanceSheetReport buildBalanceSheet t@TestDeal{ pool = pool, bonds = bndMap , fees = feeMap , liqProvider = liqMap, rateSwap = rsMap ,accounts = accMap} + rc d = let --- accounts @@ -82,7 +83,7 @@ buildBalanceSheet t@TestDeal{ pool = pool, bonds = bndMap , fees = feeMap , liqP mapPoolKey PoolConsol = Nothing mapPoolKey (PoolName x) = Just [PoolName x] poolAstBalMap_ = Map.mapWithKey - (\k _ -> getPoolBalanceStats t d (mapPoolKey k)) $ + (\k _ -> getPoolBalanceStats t rc d (mapPoolKey k)) $ view (dealPool . poolTypePool) t ---- swaps @@ -108,10 +109,10 @@ buildBalanceSheet t@TestDeal{ pool = pool, bonds = bndMap , fees = feeMap , liqP let poolAst = ParentItem "Pool" $ Map.elems poolAstMap -- Asset : Account, pool, swap to collect let ast = ParentItem "Asset" [ParentItem "Account" accM , poolAst , swapToCollect] - feeWithDueAmount <- (F.feeDue <$>) <$> mapM ((calcDueFee t d)) feeMap + feeWithDueAmount <- (F.feeDue <$>) <$> mapM ((calcDueFee t rc d)) feeMap let feeToPay = ParentItem "Fee" [ ParentItem feeName [Item "Due" feeDueBal] | (feeName,feeDueBal) <- Map.toList feeWithDueAmount ] - bndWithDueAmount <- mapM (calcDueInt t d) bndMap + bndWithDueAmount <- mapM (calcDueInt t rc d) bndMap let bndToShow = Map.map (\bnd -> (L.getCurBalance bnd, L.getTotalDueInt bnd)) bndWithDueAmount let bndM = [ ParentItem bndName [Item "Balance" bndBal,Item "Due Int" bndDueAmt ] | (bndName,(bndBal,bndDueAmt)) <- Map.toList bndToShow] diff --git a/test/DealTest/DealTest.hs b/test/DealTest/DealTest.hs index 0bd36cd2..e008fd1b 100644 --- a/test/DealTest/DealTest.hs +++ b/test/DealTest/DealTest.hs @@ -60,89 +60,94 @@ baseCase = D.TestDeal { [("General", (A.Account { A.accName="General" ,A.accBalance=1000.0 ,A.accType=Nothing, A.accInterest=Nothing ,A.accStmt=Nothing }))]) ,D.fees = Map.empty ,D.bonds = (Map.fromList [("A" - ,L.Bond{ - L.bndName="A" - ,L.bndType=L.Sequential - ,L.bndOriginInfo= L.OriginalInfo{ - L.originBalance=3000 - ,L.originDate= (T.fromGregorian 2022 1 1) - ,L.originRate= 0.08 - ,L.maturityDate = Nothing} - ,L.bndInterestInfo= L.Fix 0.08 DC_ACT_365F - ,L.bndBalance=3000 - ,L.bndRate=0.08 - ,L.bndDuePrin=0.0 - ,L.bndStepUp = Nothing - ,L.bndDueInt=0.0 - ,L.bndDueIntDate=Nothing - ,L.bndLastIntPay = Just (T.fromGregorian 2022 1 1) - ,L.bndLastPrinPay = Just (T.fromGregorian 2022 1 1) - ,L.bndStmt=Nothing - ,L.bndDueIntOverInt = 0}) - ] - ) - ,D.pool = D.MultiPool $ - (Map.fromList [(PoolConsol, (P.Pool {P.assets=[AB.Mortgage - AB.MortgageOriginalInfo{ - AB.originBalance=4000 - ,AB.originRate=Fix DC_ACT_365F 0.085 - ,AB.originTerm=60 - ,AB.period=Monthly - ,AB.startDate=T.fromGregorian 2022 1 1 - ,AB.prinType= AB.Level - ,AB.prepaymentPenalty = Nothing} - 4000 - 0.085 - 60 - Nothing - AB.Current] - ,P.futureCf=Nothing - ,P.asOfDate = T.fromGregorian 2022 1 1 - ,P.issuanceStat = Just $ Map.fromList [(IssuanceBalance, 4000)] - ,P.extendPeriods = Nothing}))]) - ,D.waterfall = Map.fromList [(W.DistributionDay Amortizing, [ - (W.PayInt Nothing "General" ["A"] Nothing) - ,(W.PayPrin Nothing "General" ["A"] Nothing) - ])] - ,D.collects = [Collect Nothing W.CollectedInterest "General" - ,Collect Nothing W.CollectedPrincipal "General"] - ,D.liqProvider = Nothing - ,D.rateCap = Nothing - ,D.triggers = Nothing - ,D.ledgers = Nothing - ,D.stats = (Map.empty,Map.empty,Map.empty,Map.empty) + ,L.Bond{ + L.bndName="A" + ,L.bndType=L.Sequential + ,L.bndOriginInfo= L.OriginalInfo{ + L.originBalance=3000 + ,L.originDate= (T.fromGregorian 2022 1 1) + ,L.originRate= 0.08 + ,L.maturityDate = Nothing} + ,L.bndInterestInfo= L.Fix 0.08 DC_ACT_365F + ,L.bndBalance=3000 + ,L.bndRate=0.08 + ,L.bndDuePrin=0.0 + ,L.bndStepUp = Nothing + ,L.bndDueInt=0.0 + ,L.bndDueIntDate=Nothing + ,L.bndLastIntPay = Just (T.fromGregorian 2022 1 1) + ,L.bndLastPrinPay = Just (T.fromGregorian 2022 1 1) + ,L.bndStmt=Nothing + ,L.bndDueIntOverInt = 0}) + ] + ) +,D.pool = D.MultiPool $ + (Map.fromList [(PoolConsol, (P.Pool {P.assets=[AB.Mortgage + AB.MortgageOriginalInfo{ + AB.originBalance=4000 + ,AB.originRate=Fix DC_ACT_365F 0.085 + ,AB.originTerm=60 + ,AB.period=Monthly + ,AB.startDate=T.fromGregorian 2022 1 1 + ,AB.prinType= AB.Level + ,AB.prepaymentPenalty = Nothing} + 4000 + 0.085 + 60 + Nothing + AB.Current] + ,P.futureCf=Nothing + ,P.asOfDate = T.fromGregorian 2022 1 1 + ,P.issuanceStat = Just $ Map.fromList [(IssuanceBalance, 4000)] + ,P.extendPeriods = Nothing}))]) + ,D.waterfall = Map.fromList [(W.DistributionDay Amortizing, [ + (W.PayInt Nothing "General" ["A"] Nothing) + ,(W.PayPrin Nothing "General" ["A"] Nothing) + ])] +,D.collects = [Collect Nothing W.CollectedInterest "General" + ,Collect Nothing W.CollectedPrincipal "General"] +,D.liqProvider = Nothing +,D.rateCap = Nothing +,D.triggers = Nothing +,D.ledgers = Nothing +,D.stats = (Map.empty,Map.empty,Map.empty,Map.empty) } baseTests = let nonRunAssump = (AP.NonPerfAssumption Nothing Nothing Nothing Nothing Nothing (Just [AP.InspectPt MonthEnd (FutureCurrentPoolBalance Nothing)]) Nothing Nothing Nothing Nothing Nothing Nothing) (dealAfterRun,poolCf,rcs,_,_) = case DR.runDeal baseCase S.empty Nothing nonRunAssump of - Left e -> error $ "Deal run failed"++ show e - Right x -> x + Left e -> error $ "Deal run failed"++ show e + Right x -> x inspects = [ rc | rc@(InspectBal {}) <- rcs ] in - testGroup "Base Deal Test" - [ testCase "empty pool flow" $ - assertEqual "empty pool flow" - 0 - -- (P.futureCf (D.pool baseCase)) - 0 - -- https://docs.google.com/spreadsheets/d/1gmz8LOB01qqfPldquyDn43PJJ1MI016tS-JS5KW3SvM/edit?gid=1325808922#gid=1325808922 - ,testCase "pool current balance (run time)" $ - assertEqual "pool current balance (run time)" - (InspectBal (toDate "20220101") (FutureCurrentPoolBalance Nothing) 4000) - (inspects!!0) - ,testCase "pool current balance (run time 1)" $ - assertEqual "pool current balance (run time 1)" - (InspectBal (toDate "20220131") (FutureCurrentPoolBalance Nothing) 4000) - (inspects!!1) - ,testCase "pool current balance (run time 2)" $ - assertEqual "pool current balance (run time 2)" - (InspectBal (toDate "20220228") (FutureCurrentPoolBalance Nothing) 3946.27) - (inspects!!2) - ,testCase "pool current balance (run time 60)" $ - assertEqual "pool current balance (run time 60)" - (InspectBal (toDate "20270131") (FutureCurrentPoolBalance Nothing) 0.0) - (inspects!!61) - ] + testGroup "Base Deal Test" + [ testCase "empty pool flow" $ + assertEqual "empty pool flow" + 0 + -- (P.futureCf (D.pool baseCase)) + 0 + -- https://docs.google.com/spreadsheets/d/1gmz8LOB01qqfPldquyDn43PJJ1MI016tS-JS5KW3SvM/edit?gid=1325808922#gid=1325808922 + ,testCase "size of inspect balance" $ + assertEqual "size of inspect balance" + 98 + (length inspects) + + ,testCase "pool current balance (run time)" $ + assertEqual "pool current balance (run time)" + (InspectBal (toDate "20220101") (FutureCurrentPoolBalance Nothing) 4000) + (inspects!!0) + ,testCase "pool current balance (run time 1)" $ + assertEqual "pool current balance (run time 1)" + (InspectBal (toDate "20220131") (FutureCurrentPoolBalance Nothing) 4000) + (inspects!!1) + ,testCase "pool current balance (run time 2)" $ + assertEqual "pool current balance (run time 2)" + (InspectBal (toDate "20220228") (FutureCurrentPoolBalance Nothing) 3946.27) + (inspects!!2) + ,testCase "pool current balance (run time 60)" $ + assertEqual "pool current balance (run time 60)" + (InspectBal (toDate "20270131") (FutureCurrentPoolBalance Nothing) 0.0) + (inspects!!61) + ] diff --git a/test/UT/AccountTest.hs b/test/UT/AccountTest.hs index 170744f6..87a79e5a 100644 --- a/test/UT/AccountTest.hs +++ b/test/UT/AccountTest.hs @@ -36,24 +36,24 @@ intTests = ,AccTxn (toDate "20220915") 150 30 Empty ]))) in testGroup "Interest on Bank Account Test" - [ - testCase "Build EarnIntAction" $ - assertEqual "QuarterEnd" - [("A1",(genSerialDates QuarterEnd Inc (toDate "20221001") 5))] $ - buildEarnIntAction [acc1] (toDate "20231231") [] - ,testCase "Build EarnIntAction Same Year" $ - assertEqual "QuarterEnd Same Year" - [("A1",(genSerialDates QuarterEnd Inc (toDate "20221001") 1))] $ - buildEarnIntAction [acc1] (toDate "20221231") [] - ,testCase "Validate Interest Calculation 1" $ - assertEqual "MonthEnd with No txn" - 200.5 - (accBalance (depositInt (toDate "20221101") acc1 )) - ,testCase "Validate Interest Calculation 2" $ - assertEqual "MonthEnd with txns" - 152.40 - (accBalance (depositInt (toDate "20221101") acc2 )) - ] + [ + testCase "Build EarnIntAction" $ + assertEqual "QuarterEnd" + [("A1",(genSerialDates QuarterEnd Inc (toDate "20221001") 5))] $ + buildEarnIntAction [acc1] (toDate "20231231") [] + ,testCase "Build EarnIntAction Same Year" $ + assertEqual "QuarterEnd Same Year" + [("A1",(genSerialDates QuarterEnd Inc (toDate "20221001") 1))] $ + buildEarnIntAction [acc1] (toDate "20221231") [] + ,testCase "Validate Interest Calculation 1" $ + assertEqual "MonthEnd with No txn" + 200.5 + (accBalance (depositInt (toDate "20221101") acc1 )) + ,testCase "Validate Interest Calculation 2" $ + assertEqual "MonthEnd with txns" + 152.40 + (accBalance (depositInt (toDate "20221101") acc2 )) + ] investTests = let @@ -64,16 +64,16 @@ investTests = ,AccTxn (toDate "20220915") 150 30 Empty ]))) in testGroup "Interest on Invest Account Test" - [ - testCase "Validate Interest Calculation 1" $ - assertEqual "MonthEnd with No txn" - 2006.66 - (accBalance (depositInt (toDate "20221101") acc1)) - ,testCase "Validate Interest Calculation 2" $ - assertEqual "MonthEnd with txns" - 152.40 - (accBalance (depositInt (toDate "20221101") acc2 )) - ] + [ + testCase "Validate Interest Calculation 1" $ + assertEqual "MonthEnd with No txn" + 2006.66 + (accBalance (depositInt (toDate "20221101") acc1)) + ,testCase "Validate Interest Calculation 2" $ + assertEqual "MonthEnd with txns" + 152.40 + (accBalance (depositInt (toDate "20221101") acc2 )) + ] reserveAccTest = @@ -81,32 +81,33 @@ reserveAccTest = acc1 = Account 200 "A1" Nothing (Just (PctReserve (CurrentPoolBalance Nothing) 0.01)) Nothing acc2 = Account 150 "A2" Nothing (Just (FixReserve 210)) Nothing accMap = Map.fromList [("A1",acc1),("A2",acc2)] + ctx = RunContext {} testCFs = CF.CashFlowFrame dummySt - [CF.MortgageFlow (toDate "20220601") 150 20 10 0 0 0 0 0 Nothing Nothing Nothing - ,CF.MortgageFlow (toDate "20220701") 130 20 10 0 0 0 0 0 Nothing Nothing Nothing - ,CF.MortgageFlow (toDate "20220801") 110 20 10 0 0 0 0 0 Nothing Nothing Nothing - ,CF.MortgageFlow (toDate "20220901") 90 20 10 0 0 0 0 0 Nothing Nothing Nothing - ,CF.MortgageFlow (toDate "20221001") 70 20 10 0 0 0 0 0 Nothing Nothing Nothing] + [CF.MortgageFlow (toDate "20220601") 150 20 10 0 0 0 0 0 Nothing Nothing Nothing + ,CF.MortgageFlow (toDate "20220701") 130 20 10 0 0 0 0 0 Nothing Nothing Nothing + ,CF.MortgageFlow (toDate "20220801") 110 20 10 0 0 0 0 0 Nothing Nothing Nothing + ,CF.MortgageFlow (toDate "20220901") 90 20 10 0 0 0 0 0 Nothing Nothing Nothing + ,CF.MortgageFlow (toDate "20221001") 70 20 10 0 0 0 0 0 Nothing Nothing Nothing] ttd = set (dealPool . poolTypePool . (ix PoolConsol) . P.poolFutureCf) (Just (testCFs, Nothing)) td2 {accounts = accMap} in testGroup "Test On Reserve Acc" - [ - testCase "Test on Pct Reserve" $ - assertEqual "shall be " - (Right 0.7) - (calcTargetAmount ttd (toDate "20220826") acc1) - ,testCase "Test on fix Reserve" $ - assertEqual "shall be " - (Right 210) - (calcTargetAmount ttd (toDate "20220801") acc2) - ,testCase "test on reserve account gap" $ - assertEqual "pct reserve gap " - (Right 0) - (queryCompound ttd (toDate "20220826") (ReserveGapAt (toDate "20220826") ["A1"])) - ,testCase "test on reserve account gap" $ - assertEqual "fix reserve gap " - (Right 60) - (queryCompound ttd (toDate "20220801") (ReserveGapAt (toDate "20220801") ["A2"])) - ] + [ + testCase "Test on Pct Reserve" $ + assertEqual "shall be " + (Right 0.7) + (calcTargetAmount ttd ctx (toDate "20220826") acc1) + ,testCase "Test on fix Reserve" $ + assertEqual "shall be " + (Right 210) + (calcTargetAmount ttd ctx (toDate "20220801") acc2) + ,testCase "test on reserve account gap" $ + assertEqual "pct reserve gap " + (Right 0) + (queryCompound ttd ctx (toDate "20220826") (ReserveGapAt (toDate "20220826") ["A1"])) + ,testCase "test on reserve account gap" $ + assertEqual "fix reserve gap " + (Right 60) + (queryCompound ttd ctx (toDate "20220801") (ReserveGapAt (toDate "20220801") ["A2"])) + ] diff --git a/test/UT/DealTest.hs b/test/UT/DealTest.hs index 67de2bc1..8e2b00b7 100644 --- a/test/UT/DealTest.hs +++ b/test/UT/DealTest.hs @@ -297,10 +297,11 @@ poolFlowTest = queryTests = testGroup "deal stat query Tests" [ let - currentDefBal = queryCompound td2 epocDate CurrentPoolDefaultedBalance + ctx = D.RunContext {} + currentDefBal = queryCompound td2 ctx epocDate CurrentPoolDefaultedBalance in - testCase "query current assets in defaulted status" $ - assertEqual "should be 200" (Right 200) currentDefBal + testCase "query current assets in defaulted status" $ + assertEqual "should be 200" (Right 200) currentDefBal ] triggerTests = testGroup "Trigger Tests" @@ -316,6 +317,7 @@ triggerTests = testGroup "Trigger Tests" ] ,Nothing) poolflowM = Map.fromList [(PoolConsol, poolflows)] + ctx = D.RunContext poolflowM Nothing Nothing ads = [PoolCollection (toDate "20220201") "" ,RunWaterfall (toDate "20220225") "" ,PoolCollection (toDate "20220301")"" @@ -328,7 +330,7 @@ triggerTests = testGroup "Trigger Tests" ,RunWaterfall (toDate "20220625") "" ,PoolCollection (toDate "20220701")"" ,RunWaterfall (toDate "20220725") "" ] - (fdeal,_,_) = case run td2 poolflowM (Just ads) Nothing Nothing Nothing DL.empty of + (fdeal,_,_) = case run td2 ctx (Just ads) Nothing DL.empty of Left _ -> error "" Right x -> x in diff --git a/test/UT/DealTest2.hs b/test/UT/DealTest2.hs index 8dc44973..754a084a 100644 --- a/test/UT/DealTest2.hs +++ b/test/UT/DealTest2.hs @@ -211,26 +211,32 @@ tdBondGroup = td { D.bonds = bondGroups, ])] } -queryTests = testGroup "Deal Group Test" - [ - let - currBndGrpBal = queryCompound tdBondGroup epocDate (CurrentBondBalanceOf ["A"]) - in - testCase "group bond balance" $ - assertEqual "should be 2500" (Right 2500) currBndGrpBal - ,let - bndsFound = D.viewDealAllBonds tdBondGroup - in - testCase "view viewDealAllBonds " $ - assertEqual "should be 3" 3 (length bndsFound) - ,let - totalBndBal = queryCompound tdBondGroup epocDate CurrentBondBalance - in - testCase "total bond balance" $ - assertEqual "should be 3000" (Right 3000) totalBndBal - ,let - originBndbal = queryCompound tdBondGroup epocDate (OriginalBondBalanceOf ["A"]) - in - testCase "original bond balance" $ - assertEqual "should be 5000" (Right 5000) originBndbal - ] +queryTests = + let + + ctx = D.RunContext {} + in + testGroup "Deal Group Test" + [ + let + + currBndGrpBal = queryCompound tdBondGroup ctx epocDate (CurrentBondBalanceOf ["A"]) + in + testCase "group bond balance" $ + assertEqual "should be 2500" (Right 2500) currBndGrpBal + ,let + bndsFound = D.viewDealAllBonds tdBondGroup + in + testCase "view viewDealAllBonds " $ + assertEqual "should be 3" 3 (length bndsFound) + ,let + totalBndBal = queryCompound tdBondGroup ctx epocDate CurrentBondBalance + in + testCase "total bond balance" $ + assertEqual "should be 3000" (Right 3000) totalBndBal + ,let + originBndbal = queryCompound tdBondGroup ctx epocDate (OriginalBondBalanceOf ["A"]) + in + testCase "original bond balance" $ + assertEqual "should be 5000" (Right 5000) originBndbal + ] diff --git a/test/UT/ExpTest.hs b/test/UT/ExpTest.hs index b25f9d6e..f18b80f2 100644 --- a/test/UT/ExpTest.hs +++ b/test/UT/ExpTest.hs @@ -14,6 +14,7 @@ import qualified Deal.DealAction as DA import qualified UT.DealTest as DT import Expense import Types +import Deal.DealBase import qualified Cashflow as CF import Debug.Trace @@ -23,12 +24,13 @@ debug = flip trace expTests = testGroup "Expense Tests" [ let - f1 = Fee "FeeName1" (RecurFee MonthFirst 50) (L.toDate "20220101") 0 Nothing 0 Nothing Nothing - f2 = Fee "FeeNameAccum" (RecurFee MonthFirst 50) (L.toDate "20220101") 60 (Just (L.toDate "20220310")) 0 Nothing Nothing - _calcDate = (L.toDate "20220310") - _calcDate2 = (L.toDate "20220115") - _calcDate3 = (L.toDate "20220415") - feesCalc = sequenceA [(DA.calcDueFee DT.td2 _calcDate f1) ,(DA.calcDueFee DT.td2 _calcDate2 f1) ,(DA.calcDueFee DT.td2 _calcDate3 f2) ,(DA.calcDueFee DT.td2 _calcDate3 f1)] + f1 = Fee "FeeName1" (RecurFee MonthFirst 50) (L.toDate "20220101") 0 Nothing 0 Nothing Nothing + f2 = Fee "FeeNameAccum" (RecurFee MonthFirst 50) (L.toDate "20220101") 60 (Just (L.toDate "20220310")) 0 Nothing Nothing + _calcDate = (L.toDate "20220310") + _calcDate2 = (L.toDate "20220115") + _calcDate3 = (L.toDate "20220415") + ctx = RunContext {} + feesCalc = sequenceA [(DA.calcDueFee DT.td2 ctx _calcDate f1) ,(DA.calcDueFee DT.td2 ctx _calcDate2 f1) ,(DA.calcDueFee DT.td2 ctx _calcDate3 f2) ,(DA.calcDueFee DT.td2 ctx _calcDate3 f1)] in testCase "calc on diff same period for recur fee" $ assertEqual @@ -37,23 +39,24 @@ expTests = testGroup "Expense Tests" ((feeDue <$>) <$> feesCalc) , let - tsPoints = [(L.TsPoint (L.toDate "20220101") 10.0) - ,(L.TsPoint (L.toDate "20220301") 15.0) - ,(L.TsPoint (L.toDate "20220601") 20.0)] - f1 = Fee "FeeName1" (FeeFlow (L.BalanceCurve tsPoints)) (L.toDate "20210101") 0 Nothing 0 Nothing Nothing - _calcDate = (L.toDate "20220321") - _calcDate2 = (L.toDate "20220621") - _calcDate3 = (L.toDate "20211221") - f1_ = Fee "FeeName1" (FeeFlow (L.BalanceCurve [(L.TsPoint (L.toDate "20220601") 20.0)])) (L.toDate "20210101") 25 (Just (L.toDate "20220321")) 0 Nothing Nothing - f2_ = f1 {feeDue = 45.0, feeDueDate = Just _calcDate2, feeType = FeeFlow (L.BalanceCurve [])} - f3_ = f1 {feeDue = 0, feeDueDate = Just _calcDate3} + tsPoints = [(L.TsPoint (L.toDate "20220101") 10.0) + ,(L.TsPoint (L.toDate "20220301") 15.0) + ,(L.TsPoint (L.toDate "20220601") 20.0)] + f1 = Fee "FeeName1" (FeeFlow (L.BalanceCurve tsPoints)) (L.toDate "20210101") 0 Nothing 0 Nothing Nothing + _calcDate = (L.toDate "20220321") + _calcDate2 = (L.toDate "20220621") + _calcDate3 = (L.toDate "20211221") + f1_ = Fee "FeeName1" (FeeFlow (L.BalanceCurve [(L.TsPoint (L.toDate "20220601") 20.0)])) (L.toDate "20210101") 25 (Just (L.toDate "20220321")) 0 Nothing Nothing + f2_ = f1 {feeDue = 45.0, feeDueDate = Just _calcDate2, feeType = FeeFlow (L.BalanceCurve [])} + f3_ = f1 {feeDue = 0, feeDueDate = Just _calcDate3} - f1WithDue = Fee "FeeName1" (FeeFlow (L.BalanceCurve tsPoints)) (L.toDate "20210101") 3 Nothing 0 Nothing Nothing - _f1WithDue = f1WithDue {feeType= FeeFlow (L.BalanceCurve [(L.TsPoint (L.toDate "20220601") 20.0)]), feeDue = 28, feeDueDate = Just _calcDate} - feesCalc = sequenceA [DA.calcDueFee DT.td2 _calcDate f1 - ,DA.calcDueFee DT.td2 _calcDate2 f1 - ,DA.calcDueFee DT.td2 _calcDate3 f1 - ,DA.calcDueFee DT.td2 _calcDate f1WithDue ] + f1WithDue = Fee "FeeName1" (FeeFlow (L.BalanceCurve tsPoints)) (L.toDate "20210101") 3 Nothing 0 Nothing Nothing + _f1WithDue = f1WithDue {feeType= FeeFlow (L.BalanceCurve [(L.TsPoint (L.toDate "20220601") 20.0)]), feeDue = 28, feeDueDate = Just _calcDate} + ctx = RunContext {} + feesCalc = sequenceA [DA.calcDueFee DT.td2 ctx _calcDate f1 + ,DA.calcDueFee DT.td2 ctx _calcDate2 f1 + ,DA.calcDueFee DT.td2 ctx _calcDate3 f1 + ,DA.calcDueFee DT.td2 ctx _calcDate f1WithDue ] in testCase "test on Custom Fee Type" $ assertEqual "calc Due Fee" (Right [f1_ , f2_ , f3_ , _f1WithDue]) feesCalc diff --git a/test/UT/RateHedgeTest.hs b/test/UT/RateHedgeTest.hs index dffbfe9e..43af9e1e 100644 --- a/test/UT/RateHedgeTest.hs +++ b/test/UT/RateHedgeTest.hs @@ -12,28 +12,31 @@ import Stmt import Deal.DealAction (accrueRC) import Data.Ratio import UT.DealTest (td2) +import Deal.DealBase as D + import Hedge (RateSwap(..),RateCap(..),RateSwapBase(..),rcNetCash) capRateTests = let rc = RateCap LIBOR6M (mkRateTs [(Lib.toDate "20240101",0.035) - ,(Lib.toDate "20250101",0.040)]) - (Fixed 1000) - (Lib.toDate "20240101") QuarterEnd (Lib.toDate "20270101") - 0.03 Nothing 0 Nothing + ,(Lib.toDate "20250101",0.040)]) + (Fixed 1000) + (Lib.toDate "20240101") QuarterEnd (Lib.toDate "20270101") + 0.03 Nothing 0 Nothing indexAssump = [RateFlat LIBOR6M 0.04] - rc1 = accrueRC td2 (Lib.toDate "20241231") indexAssump rc + ctx = D.RunContext {} + rc1 = accrueRC td2 ctx (Lib.toDate "20241231") indexAssump rc in testGroup "Cap Rate Tests" [ testCase "Accure out of scope" $ assertEqual "before" (Right rc) - (accrueRC td2 (Lib.toDate "20231201") indexAssump rc) + (accrueRC td2 ctx (Lib.toDate "20231201") indexAssump rc) ,testCase "Accure out of scope" $ assertEqual "after" (Right rc) - (accrueRC td2 (Lib.toDate "20280101") indexAssump rc) + (accrueRC td2 ctx (Lib.toDate "20280101") indexAssump rc) ,testCase "Accrue on flat curve" $ assertEqual "netCash" (Right 5.0) From 23570e0b7966b804287754497eda6640740fd0a5 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Fri, 6 Mar 2026 22:48:44 +0800 Subject: [PATCH 13/17] PoolAccruedInterest --- CHANGELOG.md | 7 +++ app/Main.hs | 41 +------------ src/Cashflow.hs | 135 ++++++++++++++++++++++++++--------------- src/Deal/DealAction.hs | 126 +++++++++++++++++++------------------- src/Deal/DealBase.hs | 9 ++- src/Deal/DealQuery.hs | 38 ++++-------- src/Deal/DealRun.hs | 1 + 7 files changed, 176 insertions(+), 181 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d827e8f..1a58f04e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ +## 0.52.3 +* NEW: add `IrrOfBond` as a formula, which return the irr of the bond +* NEW: add `IsAnyOutstanding` as formula: return `True` if any of the bond is outstanding +* NEW: add `PoolAccruedInterest` as a formula: return the accural amount of the pool + +* ENHANCEMENT: allow `negative amount` when calculating `AmountRequiredForIRR` + ## 0.51.6 ### 2025-09-05 * NEW: add new integer formula `activeBondNumber` diff --git a/app/Main.hs b/app/Main.hs index a751afab..e5d6cb2b 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -268,15 +268,6 @@ modifyDealType dm f (UDeal d) = UDeal $ DM.modDeal dm f d modifyDealType dm f (VDeal d) = VDeal $ DM.modDeal dm f d modifyDealType dm f (PDeal d) = PDeal $ DM.modDeal dm f d -queryDealType :: DealType -> Date -> DealStats -> Either String Rational -queryDealType (MDeal _d) = Q.queryCompound _d -queryDealType (RDeal _d) = Q.queryCompound _d -queryDealType (IDeal _d) = Q.queryCompound _d -queryDealType (LDeal _d) = Q.queryCompound _d -queryDealType (FDeal _d) = Q.queryCompound _d -queryDealType (UDeal _d) = Q.queryCompound _d -queryDealType (VDeal _d) = Q.queryCompound _d -queryDealType (PDeal _d) = Q.queryCompound _d queryClosingDate :: DealType -> Either String Date queryClosingDate (MDeal _d) = DD.getClosingDate (DB.dates _d) @@ -289,25 +280,7 @@ queryClosingDate (VDeal _d) = DD.getClosingDate (DB.dates _d) queryClosingDate (PDeal _d) = DD.getClosingDate (DB.dates _d) -queryDealTypeBool :: DealType -> Date -> DealStats -> Either String Bool -queryDealTypeBool (MDeal _d) d s = Q.queryDealBool _d s d -queryDealTypeBool (RDeal _d) d s = Q.queryDealBool _d s d -queryDealTypeBool (IDeal _d) d s = Q.queryDealBool _d s d -queryDealTypeBool (LDeal _d) d s = Q.queryDealBool _d s d -queryDealTypeBool (FDeal _d) d s = Q.queryDealBool _d s d -queryDealTypeBool (UDeal _d) d s = Q.queryDealBool _d s d -queryDealTypeBool (VDeal _d) d s = Q.queryDealBool _d s d -queryDealTypeBool (PDeal _d) d s = Q.queryDealBool _d s d - -testDealTypeBool :: DealType -> Date -> Pre -> Either String Bool -testDealTypeBool (MDeal _d) d p = Q.testPre d _d p -testDealTypeBool (RDeal _d) d p = Q.testPre d _d p -testDealTypeBool (IDeal _d) d p = Q.testPre d _d p -testDealTypeBool (LDeal _d) d p = Q.testPre d _d p -testDealTypeBool (FDeal _d) d p = Q.testPre d _d p -testDealTypeBool (UDeal _d) d p = Q.testPre d _d p -testDealTypeBool (VDeal _d) d p = Q.testPre d _d p -testDealTypeBool (PDeal _d) d p = Q.testPre d _d p + getDealBondMap :: DealType -> Map.Map BondName L.Bond getDealBondMap (MDeal d) = DB.bonds d @@ -395,18 +368,6 @@ evalRootFindStop (BondMetTargetIrr bn target) (dt,_,_,pResult,osPflow) Nothing -> -1 -- `debug` ("No IRR found for bond:"++ show bn) Just irr -> (fromRational . toRational) $ irr - target -- `debug` ("IRR for bond:"++ show target ++" is "++ show irr) -evalRootFindStop (BalanceFormula ds targetBal) (dt,collectedFlow,logs,_,osPflow) - = let - _date = case find (\(EndRun d msg) -> True) (reverse logs) of - Just (EndRun (Just d) _ ) -> d - Nothing -> case queryClosingDate dt of - Right d' -> d' - Left err -> error $ "Error in BalanceFormula: " ++ err - v = case queryDealType dt _date (Q.patchDateToStats _date ds) of - Right v' -> fromRational v' - Left err -> error $ "Error in BalanceFormula: " ++ err - in - (fromRational . toRational) $ v - targetBal -- `debug` ("querydate" ++ show _date++"iteration" ++ show v ++ " target:" ++ show targetBal ++ ">> " ++ show ( v- targetBal)) diff --git a/src/Cashflow.hs b/src/Cashflow.hs index 1473fba2..532c0ac9 100644 --- a/src/Cashflow.hs +++ b/src/Cashflow.hs @@ -27,15 +27,15 @@ module Cashflow (CashFlowFrame(..),Principals,Interests,Amount ,splitCashFlowFrameByDate, mergePoolCf2, buildBegBal, extendCashFlow, patchBalance ,splitPoolCashflowByDate ,getAllDatesCashFlowFrame,splitCf, cutoffCashflow - ,AssetCashflow,PoolCashflow - ,emptyCashflow,isEmptyRow2,appendMCashFlow + ,AssetCashflow,PoolCashflow,calcAccrueIntByNextTxn + ,emptyCashflow,isEmptyRow2,appendMCashFlow,getIntFlow ) where import Data.Time (Day) import Data.Fixed import Lib (weightedBy,toDate,getIntervalFactors,daysBetween,paySeqLiabilitiesAmt) import Util (mulBR,mulBInt,mulIR,lastOf) -import DateUtil ( splitByDate ) +import DateUtil ( splitByDate ,yearCountFraction ) import Types import qualified Data.Map as Map import qualified Data.Time as T @@ -60,7 +60,7 @@ import qualified Text.Tabular as TT import qualified Text.Tabular.AsciiArt as A import Control.Lens hiding (element) import Control.Lens.TH - +import Control.Monad debug = flip trace type Delinquent = Balance @@ -84,6 +84,8 @@ instance Semigroup CashFlowFrame where CashFlowFrame (begBal1, begDate1, mAccInt1) ts1 <> CashFlowFrame (begBal2, begDate2, mAccInt2) ts2 = CashFlowFrame (begBal1,begDate1,mAccInt1) (ts1 <> ts2) + + opStats :: (Balance -> Balance -> Balance) -> Maybe CumulativeStat -> Maybe CumulativeStat -> Maybe CumulativeStat opStats op (Just (a1,b1,c1,d1,e1,f1)) (Just (a2,b2,c3,d2,e2,f2)) = Just (op a1 a2,op b1 b2,op c1 c3,op d1 d2,op e1 e2,op f1 f2) opStats op Nothing Nothing = Nothing @@ -112,14 +114,16 @@ type FeePaid = Balance data TsRow = CashFlow Date Amount - | BondFlow Date Balance Principal Interest - | MortgageFlow Date Balance Principal Interest Prepayment Default Recovery Loss IRate (Maybe BorrowerNum) (Maybe PrepaymentPenalty) (Maybe CumulativeStat) - | MortgageDelinqFlow Date Balance Principal Interest Prepayment Delinquent Default Recovery Loss IRate (Maybe BorrowerNum) (Maybe PrepaymentPenalty) (Maybe CumulativeStat) - | LoanFlow Date Balance Principal Interest Prepayment Default Recovery Loss IRate (Maybe CumulativeStat) - | LeaseFlow Date Balance Rental Default - | FixedFlow Date Balance NewDepreciation Depreciation Balance Balance -- unit cash - | ReceivableFlow Date Balance AccuredFee Principal FeePaid Default Recovery Loss (Maybe CumulativeStat) - deriving(Show,Eq,Ord,Generic,NFData) + | BondFlow Date Balance Principal Interest + | MortgageFlow Date Balance Principal Interest Prepayment Default Recovery Loss IRate (Maybe BorrowerNum) (Maybe PrepaymentPenalty) (Maybe CumulativeStat) + | MortgageDelinqFlow Date Balance Principal Interest Prepayment Delinquent Default Recovery Loss IRate (Maybe BorrowerNum) (Maybe PrepaymentPenalty) (Maybe CumulativeStat) + | LoanFlow Date Balance Principal Interest Prepayment Default Recovery Loss IRate (Maybe CumulativeStat) + | LeaseFlow Date Balance Rental Default + | FixedFlow Date Balance NewDepreciation Depreciation Balance Balance -- unit cash + | ReceivableFlow Date Balance AccuredFee Principal FeePaid Default Recovery Loss (Maybe CumulativeStat) + deriving(Show,Eq,Ord,Generic,NFData) + + instance Semigroup TsRow where CashFlow d1 a1 <> (CashFlow d2 a2) = CashFlow (max d1 d2) (a1 + a2) @@ -155,17 +159,17 @@ scaleTsRow r (CashFlow d a) = CashFlow d (fromRational r * a) scaleTsRow r (BondFlow d b p i) = BondFlow d (fromRational r * b) (fromRational r * p) (fromRational r * i) scaleTsRow r (MortgageFlow d b p i prep def rec los rat mbn pp st) = MortgageFlow d - (fromRational r * b) - (fromRational r * p) - (fromRational r * i) - (fromRational r * prep) - (fromRational r * def) - (fromRational r * rec) - (fromRational r * los) - rat - mbn - pp - (splitStats r <$> st) + (fromRational r * b) + (fromRational r * p) + (fromRational r * i) + (fromRational r * prep) + (fromRational r * def) + (fromRational r * rec) + (fromRational r * los) + rat + mbn + pp + (splitStats r <$> st) scaleTsRow r (MortgageDelinqFlow d b p i prep delinq def rec los rat mbn pp st) = MortgageDelinqFlow d (fromRational r * b) @@ -193,8 +197,8 @@ type BeginDate = Date type BeginStatus = (BeginBalance, BeginDate, AccuredInterest) data CashFlowFrame = CashFlowFrame BeginStatus [TsRow] - | DummyCF - deriving (Eq,Generic,Ord) + | DummyCF + deriving (Eq,Generic,Ord) cfBeginStatus :: Lens' CashFlowFrame BeginStatus cfBeginStatus = lens getter setter @@ -261,13 +265,33 @@ cfInsertHead :: TsRow -> CashFlowFrame -> CashFlowFrame cfInsertHead tr (CashFlowFrame st trs) = CashFlowFrame st $ tr:trs +calcAccrueIntByNextTxn :: DayCount -> Date -> TsRow -> Balance +calcAccrueIntByNextTxn dc sd tr@(MortgageFlow d bal _ int _ _ _ _ r _ _ _) = int - mulBR (mflowBegBalance tr) (yearCountFraction dc sd d * (toRational r)) +calcAccrueIntByNextTxn dc sd tr@(LoanFlow d bal _ int _ _ _ _ r _) = int - mulBR (mflowBegBalance tr) (yearCountFraction dc sd d * (toRational r)) +calcAccrueIntByNextTxn dc sd tr@(MortgageDelinqFlow d bal _ int _ _ _ _ _ r _ _ _) = int - mulBR (mflowBegBalance tr) (yearCountFraction dc sd d * (toRational r)) +calcAccrueIntByNextTxn _ _ _ = 0 + +-- getIntFlow + +-- ^ split cashflow by date, update the status of right cashflow frame splitCashFlowFrameByDate :: CashFlowFrame -> Date -> SplitType -> (CashFlowFrame,CashFlowFrame) -splitCashFlowFrameByDate (CashFlowFrame status txns) d st +splitCashFlowFrameByDate (CashFlowFrame status@(begBal, begDate, begInt) txns) d st = let (ls,rs) = splitByDate txns d st + dc = DC_ACT_365F + mAccruedInt = case (ls , rs) of + ([], []) -> Nothing + ([], r:_) -> + if d <= begDate then + begInt + else + Just (calcAccrueIntByNextTxn dc d r) + (previosRs, []) -> Nothing + (previousRs, r:futureRs) -> Just (calcAccrueIntByNextTxn dc d r) + newStatus = case rs of - [] -> (0, d, Nothing) - (r:_) -> (buildBegBal rs, d, Nothing) + [] -> (0, d, mAccruedInt) + (r:_) -> (buildBegBal rs, d, mAccruedInt) in (CashFlowFrame status ls,CashFlowFrame newStatus rs) @@ -604,6 +628,28 @@ mflowInterest (MortgageFlow _ _ _ x _ _ _ _ _ _ _ _) = x mflowInterest (LoanFlow _ _ _ x _ _ _ _ _ _) = x mflowInterest x = error $ "not supported: getting interest from row" ++ show x + +viewTsRow :: Date -> TsRow -> TsRow +-- ^ take a snapshot of a record from record balance/stats and a new date +viewTsRow _d (MortgageDelinqFlow a b c d e f g h i j k l m) = MortgageDelinqFlow _d b 0 0 0 0 0 0 0 j k l m +viewTsRow _d (MortgageFlow a b c d e f g h i j k l) = MortgageFlow _d b 0 0 0 0 0 0 i j k l +viewTsRow _d (LoanFlow a b c d e f g i j k) = LoanFlow _d b 0 0 0 0 0 0 j k +viewTsRow _d (LeaseFlow a b c d) = LeaseFlow _d b 0 0 +viewTsRow _d (FixedFlow a b c d e f ) = FixedFlow _d b 0 0 0 0 +viewTsRow _d (BondFlow a b c d) = BondFlow _d b 0 0 +viewTsRow _d (ReceivableFlow a b c d e f g h i) = ReceivableFlow _d b 0 0 0 0 0 0 i + +mflowBegBalance :: TsRow -> Balance +mflowBegBalance (BondFlow _ x p _) = x + p +mflowBegBalance (MortgageDelinqFlow _ x p _ ppy delinq def _ _ _ _ _ _) = x + p + ppy + delinq +mflowBegBalance (MortgageFlow _ x p _ ppy def _ _ _ _ _ _) = x + p + ppy + def +mflowBegBalance (LoanFlow _ x p _ ppy def _ _ _ _) = x + p + ppy + def +mflowBegBalance (LeaseFlow _ b r def ) = b + r + def +mflowBegBalance (FixedFlow a b c d e f ) = b + c +mflowBegBalance (ReceivableFlow _ x _ b f def _ _ _) = x + b + def + f + + + mflowPrepayment :: TsRow -> Balance mflowPrepayment (MortgageFlow _ _ _ _ x _ _ _ _ _ _ _) = x mflowPrepayment (MortgageDelinqFlow _ _ _ _ x _ _ _ _ _ _ _ _) = x @@ -647,14 +693,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 -mflowBegBalance :: TsRow -> Balance -mflowBegBalance (BondFlow _ x p _) = x + p -mflowBegBalance (MortgageDelinqFlow _ x p _ ppy delinq def _ _ _ _ _ _) = x + p + ppy + delinq -mflowBegBalance (MortgageFlow _ x p _ ppy def _ _ _ _ _ _) = x + p + ppy + def -mflowBegBalance (LoanFlow _ x p _ ppy def _ _ _ _) = x + p + ppy + def -mflowBegBalance (LeaseFlow _ b r def ) = b + r + def -mflowBegBalance (FixedFlow a b c d e f ) = b + c -mflowBegBalance (ReceivableFlow _ x _ b f def _ _ _) = x + b + def + f mflowLoss :: TsRow -> Balance mflowLoss (MortgageFlow _ _ _ _ _ _ _ x _ _ _ _) = x @@ -712,10 +750,10 @@ 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 -> (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 + 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 emptyTsRow :: Date -> TsRow -> TsRow -- ^ reset all cashflow fields to zero and init with a date @@ -736,16 +774,6 @@ extendCashFlow d (CashFlowFrame st txns) in CashFlowFrame st (txns ++ [newTxn]) -viewTsRow :: Date -> TsRow -> TsRow --- ^ take a snapshot of a record from record balance/stats and a new date -viewTsRow _d (MortgageDelinqFlow a b c d e f g h i j k l m) = MortgageDelinqFlow _d b 0 0 0 0 0 0 0 j k l m -viewTsRow _d (MortgageFlow a b c d e f g h i j k l) = MortgageFlow _d b 0 0 0 0 0 0 i j k l -viewTsRow _d (LoanFlow a b c d e f g i j k) = LoanFlow _d b 0 0 0 0 0 0 j k -viewTsRow _d (LeaseFlow a b c d) = LeaseFlow _d b 0 0 -viewTsRow _d (FixedFlow a b c d e f ) = FixedFlow _d b 0 0 0 0 -viewTsRow _d (BondFlow a b c d) = BondFlow _d b 0 0 -viewTsRow _d (ReceivableFlow a b c d e f g h i) = ReceivableFlow _d b 0 0 0 0 0 0 i - -- ^ given a cashflow,build a new cf row with begin balance buildBegTsRow :: Date -> TsRow -> TsRow buildBegTsRow d flow@FixedFlow{} = flow @@ -1179,5 +1207,12 @@ txnCumulativeStats = lens getter setter = ReceivableFlow d bal p i ppy def recovery loss mStat setter x _ = x +getIntFlow :: TsRow -> Maybe Balance +getIntFlow (MortgageDelinqFlow _ _ _ i _ _ _ _ _ _ _ _ _) = Just i +getIntFlow (MortgageFlow _ _ _ i _ _ _ _ _ _ _ _) = Just i +getIntFlow (LoanFlow _ _ _ i _ _ _ _ _ _) = Just i +getIntFlow _ = Nothing + + $(deriveJSON defaultOptions ''TsRow) $(deriveJSON defaultOptions ''CashFlowFrame) diff --git a/src/Deal/DealAction.hs b/src/Deal/DealAction.hs index bf894275..2981c9f5 100644 --- a/src/Deal/DealAction.hs +++ b/src/Deal/DealAction.hs @@ -2,9 +2,9 @@ {-# LANGUAGE ScopedTypeVariables #-} module Deal.DealAction (performActionWrap,performAction,calcDueFee - ,testTrigger,RunContext(..),updateLiqProvider - ,calcDueInt,priceAssetUnion - ,priceAssetUnionList,inspectVars,inspectListVars,accrueRC,accrueDeal) + ,testTrigger,RunContext(..),updateLiqProvider + ,calcDueInt,priceAssetUnion + ,priceAssetUnionList,inspectVars,inspectListVars,accrueRC,accrueDeal) where import qualified Accounts as A @@ -348,11 +348,11 @@ accrueDeal d ras t@TestDeal{fees = feeMap, bonds = bondMap, liqProvider = liqMap feeMap' <- sequenceA $ Map.map (\v -> if (F.reAccruableFeeType (F.feeType v)) then calcDueFee t rc d v else pure v) feeMap rcMap' <- traverse (Map.traverseWithKey (\_ -> accrueRC t rc d ras)) rcMap return t { fees = feeMap' , - bonds = bondMap', - liqProvider = liqMap', - rateSwap = rsMap', - rateCap = rcMap' - } + bonds = bondMap', + liqProvider = liqMap', + rateSwap = rsMap', + rateCap = rcMap' + } @@ -423,7 +423,6 @@ updateOriginDate2 d (ACM.LS m) = ACM.LS $ updateOriginDate m (Ast.calcAlignDate updateOriginDate2 d (ACM.RE m) = ACM.RE $ updateOriginDate m (Ast.calcAlignDate m d) - sumSupport :: [SupportAvailType] -> SupportAvailType sumSupport [] = ByAvailAmount 0 sumSupport [ByAvailAmount b1] = ByAvailAmount b1 @@ -511,17 +510,17 @@ inspectVars :: Ast.Asset a => TestDeal a -> RunContext -> Date -> DealStats -> E inspectVars t rc d ds = case getDealStatType ds of RtnRate -> do - q <- queryCompound t rc d (patchDateToStats d ds) - return $ InspectRate d ds $ fromRational q + q <- queryCompound t rc d (patchDateToStats d ds) + return $ InspectRate d ds $ fromRational q RtnBool -> do - q <- queryDealBool t rc (patchDateToStats d ds) d - return $ InspectBool d ds q + q <- queryDealBool t rc (patchDateToStats d ds) d + return $ InspectBool d ds q RtnInt -> do - q <- queryCompound t rc d (patchDateToStats d ds) - return $ InspectInt d ds $ round . fromRational $ q + q <- queryCompound t rc d (patchDateToStats d ds) + return $ InspectInt d ds $ round . fromRational $ q _ -> do - q <- queryCompound t rc d (patchDateToStats d ds) - return $ InspectBal d ds $ fromRational q + q <- queryCompound t rc d (patchDateToStats d ds) + return $ InspectBal d ds $ fromRational q showInspection :: ResultComponent -> String showInspection (InspectRate d ds r) = show r @@ -543,8 +542,8 @@ applyLimit t rc d availBal dueBal (Just limit) = case limit of DueCapAmt amt -> return $ min amt availBal DS ds -> do - v <- queryCompound t rc d (patchDateToStats d ds) - return (min (fromRational v) availBal) + v <- queryCompound t rc d (patchDateToStats d ds) + return (min (fromRational v) availBal) DuePct pct -> return $ min availBal $ mulBR dueBal pct x -> Left $ "Date:"++show d ++" Unsupported limit found:"++ show x @@ -567,8 +566,8 @@ calcAvailAfterLimit t rc d acc Nothing dueAmt (Just limit) txnAmt <- case limit of DueCapAmt amt -> return $ min amt afterDueAmt DS ds -> do - v <- queryCompound t rc d (patchDateToStats d ds) - return $ min (fromRational v) afterDueAmt + v <- queryCompound t rc d (patchDateToStats d ds) + return $ min (fromRational v) afterDueAmt DuePct pct -> return $ min (mulBR afterDueAmt pct) afterDueAmt _ -> Left ("Failed to find type"++ show limit) return (txnAmt, txnAmt, 0) @@ -799,7 +798,7 @@ performAction d t@TestDeal{accounts=accMap, ledgers = Just ledgerM} rc (sourceAcc', targetAcc') <- A.transfer (sourceAcc,targetAcc) d transferAmt let newLedgerM = Map.adjust (LD.entryLogByDr (dr, transferAmt) d Nothing) lName ledgerM return t {accounts = Map.insert an1 sourceAcc' (Map.insert an2 targetAcc' accMap) - , ledgers = Just newLedgerM} + , ledgers = Just newLedgerM} performAction d t@TestDeal{accounts=accMap} rc (W.Transfer mLimit an1 an2 mComment) = do @@ -843,9 +842,9 @@ performAction d t@TestDeal{ledgers= Just ledgerM} rc (W.BookBy (W.PDL dr ds ledg ledgCaps <- sequenceA [ queryCompound t rc d ledgerCap | ledgerCap <- snd <$> ledgersList ] let amtBookedToLedgers = paySeqLiabilitiesAmt (fromRational amtToBook) (fromRational <$> ledgCaps) let newLedgerM = foldr - (\(ln,amt) acc -> Map.adjust (LD.entryLogByDr (dr,amt) d Nothing) ln acc) - ledgerM - (zip ledgerNames amtBookedToLedgers) --`debug` ("amts to book"++ show amtBookedToLedgers) + (\(ln,amt) acc -> Map.adjust (LD.entryLogByDr (dr,amt) d Nothing) ln acc) + ledgerM + (zip ledgerNames amtBookedToLedgers) return $ t {ledgers = Just newLedgerM} -- ^ pay fee sequentially, but not accrued @@ -906,41 +905,41 @@ performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} rc performAction d t@TestDeal{bonds=bndMap, accounts=accMap, liqProvider=liqMap} rc (W.PayIntBySeq mLimit an bnds mSupport) - = let - q = DueTotalOf [DueArrears, DueInterest Nothing] - qFn = getDueBal d (Just q) - in - do - acc <- lookupM an accMap - bndsList <- lookupVs bnds bndMap - let dueAmts = qFn <$> bndsList - let totalDue = sum dueAmts - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit - (bondsPaid,_) <- paySeqM d paidOutAmt qFn (pay d q) (Right []) bndsList - newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap - let dealAfterAcc = t {accounts = newAccMap - ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} - updateSupport d mSupport supportPaidOut dealAfterAcc rc + = let + q = DueTotalOf [DueArrears, DueInterest Nothing] + qFn = getDueBal d (Just q) + in + do + acc <- lookupM an accMap + bndsList <- lookupVs bnds bndMap + let dueAmts = qFn <$> bndsList + let totalDue = sum dueAmts + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit + (bondsPaid,_) <- paySeqM d paidOutAmt qFn (pay d q) (Right []) bndsList + newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap + let dealAfterAcc = t {accounts = newAccMap + ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} + updateSupport d mSupport supportPaidOut dealAfterAcc rc performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayIntOverInt mLimit an bnds mSupport) - = let - q = DueArrears - qFn = getDueBal d (Just q) - in - do - acc <- lookupM an accMap - bndsList <- lookupVs bnds bndMap - let dueAmts = qFn <$> bndsList - let totalDue = sum dueAmts - (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit - (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList - newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap - let dealAfterAcc = t {accounts = newAccMap - ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} - - updateSupport d mSupport supportPaidOut dealAfterAcc rc + = let + q = DueArrears + qFn = getDueBal d (Just q) + in + do + acc <- lookupM an accMap + bndsList <- lookupVs bnds bndMap + let dueAmts = qFn <$> bndsList + let totalDue = sum dueAmts + (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit + (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList + newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap + let dealAfterAcc = t {accounts = newAccMap + ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap} + + updateSupport d mSupport supportPaidOut dealAfterAcc rc performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayInt mLimit an bnds mSupport) @@ -974,7 +973,6 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap,ledgers= Just ledgerM} r (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList let newLedgerM = Map.adjust (LD.entryLogByDr (dr,paidOutAmt) d Nothing) lName ledgerM newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap - let dealAfterAcc = t {accounts = newAccMap ,bonds = Map.fromList (zip bnds bondsPaid) <> bndMap ,ledgers = Just newLedgerM} @@ -1122,7 +1120,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayIntGroup mLimi bndMapAfterPay <- mapM (\(bnd, amt) -> pay d q amt bnd) payOutPlan newAccMap <- adjustM (A.draw d accPaidOut (PayGroupInt bndsToPayNames)) an accMap let dealAfterAcc = t {accounts = newAccMap - ,bonds = Map.insert + ,bonds = Map.insert bndGrpName (L.BondGroup ((lstToMapByFn L.bndName bndMapAfterPay) <> bndsMap) pt) bndMap} @@ -1460,12 +1458,12 @@ performAction d t@TestDeal{rateSwap = Just rtSwap, accounts = accsMap } rc (W.Sw rSwap <- lookupM sName rtSwap if (HE.rsNetCash (rtSwap Map.! sName)) < 0 then let - payoutAmt = negate $ HE.rsNetCash $ rSwap - availBal = A.accBalance $ acc - amtToPay = min payoutAmt availBal - newRtSwap = Map.adjust (HE.payoutIRS d amtToPay) sName rtSwap - in - do + payoutAmt = negate $ HE.rsNetCash $ rSwap + availBal = A.accBalance $ acc + amtToPay = min payoutAmt availBal + newRtSwap = Map.adjust (HE.payoutIRS d amtToPay) sName rtSwap + in + do newAccMap <- adjustM (A.draw d amtToPay (SwapOutSettle sName)) accName accsMap return $ t { rateSwap = Just newRtSwap, accounts = newAccMap } else diff --git a/src/Deal/DealBase.hs b/src/Deal/DealBase.hs index 8aba7788..e68a983e 100644 --- a/src/Deal/DealBase.hs +++ b/src/Deal/DealBase.hs @@ -16,7 +16,7 @@ module Deal.DealBase (TestDeal(..),SPV(..),dealBonds,dealFees,dealAccounts,dealP ,DealStatFields(..),getDealStatInt,isPreClosing,populateDealDates ,bondTraversal,findBondByNames,updateBondInMap,traverseBondMap,traverseBondMapByFn ,_MultiPool,_ResecDeal,uDealFutureCf,uDealFutureScheduleCf - ,RunContext(..) + ,RunContext(..),getNextPoolCfTxn ) where import qualified Accounts as A @@ -214,7 +214,12 @@ data RunContext = RunContext{ } deriving (Show) - +getNextPoolCfTxn :: Map.Map PoolId CF.PoolCashflow -> Map.Map PoolId (Maybe CF.TsRow) +getNextPoolCfTxn poolCfMap = Map.map (\case + ((CF.CashFlowFrame (_,_,_) []),_) -> Nothing --TODO it looks can be deleted + ((CF.CashFlowFrame (_,_,_) txns),_) -> fst <$> Data.List.uncons txns + ) + poolCfMap populateDealDates :: DateDesp -> DealStatus -> Either ErrorRep (Date,Date,Date,PoolCollectionActions,BondDistributionActions,Date,CustomActions) populateDealDates (PreClosingDates cutoff closing mRevolving end (firstCollect,poolDp) (firstPay,bondDp)) _ diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index b888e1b0..233e968d 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -780,34 +780,22 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f A.calcIRR ds vs FuturePoolAccruedInterest d mPns -> - -- TODO https://github.com/absbox/Hastructure/issues/316 - -- TODO it won't work for bonds as underlying assets(resec deals) + -- outstandingFlow let - pCf::(Map.Map PoolId (Maybe CF.TsRow)) = getLatestCollectFrame t mPns -- `debug` ("mPns"++ show mPns) - - accrueIntFn :: PoolId -> Maybe CF.TsRow -> Balance - accrueIntFn pid Nothing = - case pt of - MultiPool poolMap -> - case Map.lookup pid poolMap of - Just pool -> case Pl.getIssuanceField pool RuntimeCurrentPoolBalance of - Right bal -> - let - accrueRate = 0.0 - sd = getCutoffDate (dates t) - in - mulBR bal ((yearCountFraction DC_ACT_365F sd d) * accrueRate) - Left _ -> 0.0 - Nothing -> 0.0 - -- TODO add support for resec deal - _ -> 0.0 - accrueIntFn _ (Just (CF.MortgageFlow sd bal _ _ _ _ _ _ r _ _ _)) = mulBR bal (yearCountFraction DC_ACT_365F sd d * (toRational r)) - accrueIntFn _ (Just (CF.MortgageDelinqFlow sd bal _ _ _ _ _ _ _ r _ _ _)) = mulBR bal (yearCountFraction DC_ACT_365F sd d * (toRational r)) - accrueIntFn _ (Just (CF.LoanFlow sd bal _ _ _ _ _ _ r _)) = mulBR bal (yearCountFraction DC_ACT_365F sd d * (toRational r)) - accrueIntFn _ (Just r) = 0.0 + pCf::(Map.Map PoolId CF.CashFlowFrame) = Map.map fst outstandingFlow + + accrueIntFn (CF.CashFlowFrame _ []) = 0.0 + accrueIntFn (CF.CashFlowFrame (begBal, sd, mAccAmt) (r:_)) + | d <= sd = 0.0 + | otherwise = CF.calcAccrueIntByNextTxn DC_ACT_365F d r + -- accrueIntFn _ (Just (CF.MortgageFlow sd bal _ int _ _ _ _ r _ _ _)) = int + -- accrueIntFn _ (Just (CF.MortgageDelinqFlow sd bal _ int _ _ _ _ _ r _ _ _)) = int + -- accrueIntFn _ (Just (CF.LoanFlow sd bal _ int _ _ _ _ r _)) = int + -- accrueIntFn _ (Just r) = 0.0 + -- accrueIntFn _ _ = 0.0 in - Right . toRational $ sum $ Map.elems $ Map.mapWithKey accrueIntFn pCf + Right . toRational $ sum $ Map.elems $ Map.map accrueIntFn pCf CustomData s d -> diff --git a/src/Deal/DealRun.hs b/src/Deal/DealRun.hs index d728bb97..a0bfc035 100644 --- a/src/Deal/DealRun.hs +++ b/src/Deal/DealRun.hs @@ -296,6 +296,7 @@ run t@TestDeal{accounts=accMap,fees=feeMap,triggers=mTrgMap,bonds=bndMap,status= ,waterfall=waterfallM,name=dealName,pool=pt,stats=_stat} rc@(RunContext poolFlowMap rAssump rates) (Just (ad:ads)) calls log + -- Ended by No Pool Cashflow/All Account is zero/Not revolving | futureCashToCollectFlag && (queryCompound t rc (getDate ad) AllAccBalance == Right 0) && (dStatus /= Revolving) && (dStatus /= Warehousing Nothing) --TODO need to use prsim here to cover all warehouse status = let endingLog = EndRun (Just (getDate ad)) "No Pool Cashflow/All Account is zero/Not revolving" From e098a63b52e272a5e31a357ef04749364f877bca Mon Sep 17 00:00:00 2001 From: yellowbean Date: Sun, 8 Mar 2026 00:18:28 +0800 Subject: [PATCH 14/17] refactor:Interface --- Hastructure.cabal | 1 + src/Accounts.hs | 1 + src/Asset.hs | 2 +- src/AssetClass/AssetBase.hs | 18 ++++++------- src/AssetClass/MixedAsset.hs | 2 +- src/Cashflow.hs | 2 +- src/CreditEnhancement.hs | 1 + src/Deal/DealAction.hs | 16 ++++++----- src/Deal/DealBase.hs | 1 + src/Deal/DealQuery.hs | 1 + src/Deal/DealRun.hs | 1 + src/Deal/DealValidation.hs | 1 + src/Expense.hs | 1 + src/Hedge.hs | 1 + src/Interface.hs | 49 +++++++++++++++++++++++++++++++--- src/Liability.hs | 1 + src/Reports.hs | 15 +++++------ src/Stmt.hs | 13 ++++++--- src/Types.hs | 51 +++--------------------------------- src/Waterfall.hs | 4 ++- swagger.json | 32 ++++++++++++++++++++++ 21 files changed, 130 insertions(+), 84 deletions(-) diff --git a/Hastructure.cabal b/Hastructure.cabal index 67a47688..e6db210e 100644 --- a/Hastructure.cabal +++ b/Hastructure.cabal @@ -58,6 +58,7 @@ library Expense Hedge InterestRate + Interface Ledger Liability Lib diff --git a/src/Accounts.hs b/src/Accounts.hs index 164a8fb8..89d3097b 100644 --- a/src/Accounts.hs +++ b/src/Accounts.hs @@ -15,6 +15,7 @@ import Types import Lib import Util import DateUtil +import Interface import Data.Aeson hiding (json) import Language.Haskell.TH import Data.Aeson.TH diff --git a/src/Asset.hs b/src/Asset.hs index 86309429..cfcca12b 100644 --- a/src/Asset.hs +++ b/src/Asset.hs @@ -8,7 +8,7 @@ module Asset ( Asset(..), buildAssumptionPpyDefRecRate,buildAssumptionPpyDelinqDefRecRate - ,calcRecoveriesFromDefault,getCurBalance + ,calcRecoveriesFromDefault ,priceAsset,applyHaircut,buildPrepayRates,buildDefaultRates,getObligorFields ,getObligorTags,getObligorId,getRecoveryLagAndRate,getDefaultDelinqAssump,getOriginInfo ) where diff --git a/src/AssetClass/AssetBase.hs b/src/AssetClass/AssetBase.hs index 54632a5a..f4fe3169 100644 --- a/src/AssetClass/AssetBase.hs +++ b/src/AssetClass/AssetBase.hs @@ -31,7 +31,6 @@ import Util import qualified Data.Map as Map import qualified InterestRate as IR import qualified Cashflow as CF --- import Assumptions (RevolvingAssumption(Dummy4)) import Control.Lens hiding (element,Index) import Control.Lens.TH @@ -54,14 +53,15 @@ data AmortPlan = Level -- ^ for mortgage / french system -> -- | calculate period payment (Annuity/Level mortgage) calcPmt :: Balance -> IRate -> Int -> Amount -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)) +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 = Balance type PrincipalAmount = Balance diff --git a/src/AssetClass/MixedAsset.hs b/src/AssetClass/MixedAsset.hs index efcee4a9..eea638c2 100644 --- a/src/AssetClass/MixedAsset.hs +++ b/src/AssetClass/MixedAsset.hs @@ -12,6 +12,7 @@ import qualified Cashflow as CF -- (Cashflow,Amount,Interests,Principals) import qualified Assumptions as A import qualified AssetClass.AssetBase as ACM import InterestRate +import Interface import qualified Asset as P import Lib import Util @@ -31,7 +32,6 @@ import AssetClass.Mortgage import AssetClass.Lease import AssetClass.Loan import AssetClass.Installment - import AssetClass.Receivable import AssetClass.AssetCashflow import AssetClass.FixedAsset diff --git a/src/Cashflow.hs b/src/Cashflow.hs index 532c0ac9..df8f75d2 100644 --- a/src/Cashflow.hs +++ b/src/Cashflow.hs @@ -266,7 +266,7 @@ cfInsertHead tr (CashFlowFrame st trs) = CashFlowFrame st $ tr:trs calcAccrueIntByNextTxn :: DayCount -> Date -> TsRow -> Balance -calcAccrueIntByNextTxn dc sd tr@(MortgageFlow d bal _ int _ _ _ _ r _ _ _) = int - mulBR (mflowBegBalance tr) (yearCountFraction dc sd d * (toRational r)) +calcAccrueIntByNextTxn dc sd tr@(MortgageFlow d bal _ int _ _ _ _ r _ _ _) = int - mulBR (mflowBegBalance tr) (yearCountFraction dc sd d * (toRational r)) -- `debug` ("calcAccrueIntByNextTxn: "++ show (int,mflowBegBalance tr ,sd,d,yearCountFraction dc sd d,r)) calcAccrueIntByNextTxn dc sd tr@(LoanFlow d bal _ int _ _ _ _ r _) = int - mulBR (mflowBegBalance tr) (yearCountFraction dc sd d * (toRational r)) calcAccrueIntByNextTxn dc sd tr@(MortgageDelinqFlow d bal _ int _ _ _ _ _ r _ _ _) = int - mulBR (mflowBegBalance tr) (yearCountFraction dc sd d * (toRational r)) calcAccrueIntByNextTxn _ _ _ = 0 diff --git a/src/CreditEnhancement.hs b/src/CreditEnhancement.hs index ef72027a..c3d5f4ef 100644 --- a/src/CreditEnhancement.hs +++ b/src/CreditEnhancement.hs @@ -16,6 +16,7 @@ import qualified Data.Time as Time import qualified Data.Map as Map import qualified Data.DList as DL import GHC.Generics +import Interface import Language.Haskell.TH import Data.Aeson hiding (json) import Data.Aeson.TH diff --git a/src/Deal/DealAction.hs b/src/Deal/DealAction.hs index 2981c9f5..2be13e8c 100644 --- a/src/Deal/DealAction.hs +++ b/src/Deal/DealAction.hs @@ -40,6 +40,7 @@ import DateUtil import Types import Revolving import Triggers +import Interface import qualified Data.Map as Map import qualified Data.Time as T @@ -239,7 +240,6 @@ updateLiqProvider t rc d liq@CE.LiqFacility{CE.liqType = liqType, CE.liqCredit = Right x -> updateSupportAvailType (min (fromRational x)) curCredit _ -> curCredit --- ^TODO : to be replace from L.accrueInt -- Not possible to use L.accrueInt, since the interest may use formula to query on deal's stats calcDueInt :: Ast.Asset a => TestDeal a -> RunContext -> Date -> L.Bond -> Either ErrorRep L.Bond calcDueInt t rc d b@(L.BondGroup bMap pt) @@ -931,8 +931,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc do acc <- lookupM an accMap bndsList <- lookupVs bnds bndMap - let dueAmts = qFn <$> bndsList - let totalDue = sum dueAmts + let totalDue = sum $ qFn <$> bndsList (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap @@ -950,8 +949,7 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc do acc <- lookupM an accMap bndsList <- lookupVs bnds bndMap - let dueAmts = qFn <$> bndsList - let totalDue = sum dueAmts + let totalDue = sum $ qFn <$> bndsList (paidOutAmt,accPaidOut,supportPaidOut) <- calcAvailAfterLimit t rc d acc mSupport totalDue mLimit (bondsPaid,_) <- payProM d paidOutAmt qFn (pay d q) bndsList newAccMap <- adjustM (A.draw d accPaidOut (PayInt bnds)) an accMap @@ -1138,8 +1136,8 @@ performAction d t@TestDeal{bonds=bndMap,accounts=accMap} rc (W.PayPrinWithDue an acc <- lookupM an accMap let actualPaidOut = min (A.accBalance acc) $ sum bndsDueAmts (bndsPaid, remainAmt) <- payProM d actualPaidOut L.bndDuePrin (pay d DuePrincipal) bndsToPay + accMapAfterPay <- adjustM (A.draw d actualPaidOut (PayPrin bnds)) an accMap let bndMapUpdated = (Map.fromList $ zip bndsToPayNames bndsPaid) <> bndMap - accMapAfterPay <- adjustM (A.draw d actualPaidOut (PayPrin bnds)) an accMap return $ t {accounts = accMapAfterPay, bonds = bndMapUpdated} @@ -1248,9 +1246,13 @@ performAction d t@TestDeal{fees=feeMap} rc (W.CalcFee fns) -- TODO wont' persert the bond shape for a bond group performAction d t@TestDeal{bonds=bndMap} rc (W.CalcBondInt bns) = do - newBondMap <- mapM (calcDueInt t rc d) $ getBondsByName t (Just bns) + -- newBondMap <- mapM (calcDueInt t rc d) $ getBondsByName t (Just bns) + -- return t {bonds = newBondMap <> bndMap} + newBondMap <- traverseBondMap bns (calcDueInt t rc d) bndMap return t {bonds = newBondMap <> bndMap} + + -- ^ set due prin mannually performAction d t@TestDeal{bonds=bndMap} rc (W.CalcBondPrin2 mLimit bnds) = do diff --git a/src/Deal/DealBase.hs b/src/Deal/DealBase.hs index e68a983e..6ea3a3c5 100644 --- a/src/Deal/DealBase.hs +++ b/src/Deal/DealBase.hs @@ -32,6 +32,7 @@ import qualified Assumptions as AP import qualified AssetClass.AssetBase as ACM import qualified Call as C import qualified InterestRate as IR +import Interface import Stmt import Lib import Util diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index 233e968d..038b9d87 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -41,6 +41,7 @@ import qualified InterestRate as IR import Stmt import Util import Errors +import Interface import DateUtil import Control.Lens hiding (element) import Control.Lens.Extras (is) diff --git a/src/Deal/DealRun.hs b/src/Deal/DealRun.hs index a0bfc035..8f019ea0 100644 --- a/src/Deal/DealRun.hs +++ b/src/Deal/DealRun.hs @@ -27,6 +27,7 @@ import qualified Hedge as HE import qualified CreditEnhancement as CE import qualified InterestRate as IR import Triggers +import Interface import Deal.DealBase import Deal.DealAction diff --git a/src/Deal/DealValidation.hs b/src/Deal/DealValidation.hs index af6f6943..c679c91d 100644 --- a/src/Deal/DealValidation.hs +++ b/src/Deal/DealValidation.hs @@ -24,6 +24,7 @@ import qualified Expense as F import qualified Asset as P import qualified Assumptions as AP import qualified InterestRate as IR +import Interface import Deal.DealCollection (CollectionRule(..)) diff --git a/src/Expense.hs b/src/Expense.hs index 741a2b85..c41dd057 100644 --- a/src/Expense.hs +++ b/src/Expense.hs @@ -25,6 +25,7 @@ import Data.Fixed import Types import Util import DateUtil +import Interface import qualified Stmt as S import qualified InterestRate as IR diff --git a/src/Hedge.hs b/src/Hedge.hs index 2bc84e86..aa3b9982 100644 --- a/src/Hedge.hs +++ b/src/Hedge.hs @@ -26,6 +26,7 @@ import Types import Util import Stmt import DateUtil +import Interface import qualified Assumptions as A import qualified InterestRate as IR diff --git a/src/Interface.hs b/src/Interface.hs index 2cdcdadb..35bbe794 100644 --- a/src/Interface.hs +++ b/src/Interface.hs @@ -1,13 +1,54 @@ {-# LANGUAGE DeriveGeneric #-} -module Interface() +module Interface(Liable(..),Accruable(..),Payable(..),RateResettable(..),Drawable(..),Collectable(..) + ,DueType(..) + ) where import GHC.Generics +import Types +class Liable lb where + -- bond / expense / liquidity facility / hedges -import Types + -- must implement + isPaidOff :: lb -> Bool + getCurBalance :: lb -> Balance + getCurRate :: lb -> IRate + getOriginBalance :: lb -> Balance + getOriginDate :: lb -> Date + getAccrueBegDate :: lb -> Date + getDueInt :: lb -> Balance + getDueIntAt :: lb -> Int -> Balance + getDueIntOverInt :: lb -> Balance + getDueIntOverIntAt :: lb -> Int -> Balance + getTotalDueInt :: lb -> Balance + getTotalDueIntAt :: lb -> Int -> Balance + getOutstandingAmount :: lb -> Balance + + + +class Accruable ac where + -- + bookAccrual :: Date -> Balance -> ac -> ac + getAccrualDates :: Date -> ac -> [Date] + accrueTo :: Date -> ac -> ac + -- accrueWithDeal :: Date -> deal -> ac -> ac + +class Payable pa where + pay :: Date -> DueType -> Balance -> pa -> Either ErrorRep pa + getDueBal :: Date -> Maybe DueType -> pa -> Balance + writeOff :: Date -> DueType -> Amount -> pa -> Either ErrorRep pa + +class RateResettable rs where + getResetDates :: Date -> rs -> [Dates] + reset :: Date -> rs -> rs + +class Drawable dr where + draw :: Date -> Balance -> TxnComment -> dr -> Either ErrorRep dr + availForDraw :: Date -> dr -> SupportAvailType +class Collectable cl where + collect :: Date -> cl -> Either ErrorRep cl + availForCollect :: Date -> cl -> Either ErrorRep Balance --- encodeDateType :: DateType -> Encode.MessageBuilder --- encodeDateType (DateType a) = Encode.int32 a diff --git a/src/Liability.hs b/src/Liability.hs index 2980e78d..ea7fb857 100644 --- a/src/Liability.hs +++ b/src/Liability.hs @@ -39,6 +39,7 @@ import Analytics import Data.Ratio import Data.Maybe import Data.List +import Interface import qualified Data.Set as Set import qualified Data.DList as DL import qualified Stmt as S diff --git a/src/Reports.hs b/src/Reports.hs index b4fc9d96..0a31416f 100644 --- a/src/Reports.hs +++ b/src/Reports.hs @@ -83,14 +83,14 @@ buildBalanceSheet t@TestDeal{ pool = pool, bonds = bndMap , fees = feeMap , liqP mapPoolKey PoolConsol = Nothing mapPoolKey (PoolName x) = Just [PoolName x] poolAstBalMap_ = Map.mapWithKey - (\k _ -> getPoolBalanceStats t rc d (mapPoolKey k)) $ - view (dealPool . poolTypePool) t + (\k _ -> getPoolBalanceStats t rc d (mapPoolKey k)) $ + view (dealPool . poolTypePool) t ---- swaps swapToCollect = ParentItem "Swap" [ ParentItem rsName [ Item "To Receive" rsNet ] | (rsName,rsNet) <- Map.toList (Map.map (HE.rsNetCash . (HE.accrueIRS d)) (fromMaybe Map.empty rsMap)) , rsNet > 0 ] - -- liquidity provider + -- liquidity provider liqProviderAccrued = Map.map (CE.accrueLiqProvider d) (fromMaybe Map.empty liqMap) liqProviderOs = [ ParentItem liqName [Item "Balance" liqBal,Item "Accrue Int" liqDueInt, Item "Due Fee" liqDueFee ] | (liqName,[liqBal,liqDueInt,liqDueFee]) <- Map.toList (Map.map (\liq -> [CE.liqBalance,CE.liqDueInt,CE.liqDuePremium]<*> [liq]) liqProviderAccrued)] -- rate swap @@ -127,14 +127,13 @@ buildBalanceSheet t@TestDeal{ pool = pool, bonds = bndMap , fees = feeMap , liqP buildCashReport :: P.Asset a => TestDeal a -> Date -> Date -> CashflowReport buildCashReport t@TestDeal{accounts = accs} sd ed = CashflowReport { inflow = inflowItems - , outflow = outflowItems - , net = cashChange - , startDate = sd - , endDate = ed } + , outflow = outflowItems + , net = cashChange + , startDate = sd + , endDate = ed } where _txns = concat $ Map.elems $ Map.map (DL.toList . getTxns) $ Map.map A.accStmt accs txns = sliceBy EI sd ed _txns - inflowTxn = sort $ filter (\x -> (getFlow . getTxnComment) x == Inflow) txns outflowTxn = sort $ filter (\x -> (getFlow . getTxnComment) x == Outflow) txns diff --git a/src/Stmt.hs b/src/Stmt.hs index 2de6afc8..54e9c69a 100644 --- a/src/Stmt.hs +++ b/src/Stmt.hs @@ -207,10 +207,10 @@ combineTxn (SupportTxn d1 b1 b0 i1 p1 c1 m1) (SupportTxn d2 b2 b02 i2 p2 c2 m2) data FlowDirection = Inflow -- cash flow into the SPV - | Outflow -- cash flow out of the SPV - | Interflow -- cash flow within the SPV - | Noneflow -- no cash flow - deriving (Eq,Show,Generic) + | Outflow -- cash flow out of the SPV + | Interflow -- cash flow within the SPV + | Noneflow -- no cash flow + deriving (Eq,Show,Generic) getFlow :: TxnComment -> FlowDirection getFlow comment = @@ -227,8 +227,10 @@ getFlow comment = LiquidationRepay _ -> Outflow SwapOutSettle _ -> Outflow PurchaseAsset _ _-> Outflow + Transfer _ _ -> Interflow TransferBy {} -> Interflow + FundWith _ _ -> Inflow PoolInflow _ _ -> Inflow LiquidationProceeds _ -> Inflow @@ -236,16 +238,19 @@ getFlow comment = BankInt -> Inflow SwapInSettle _ -> Inflow IssuanceProceeds _ -> Inflow + LiquidationDraw -> Noneflow LiquidationSupportInt _ _ -> Noneflow WriteOff _ _ -> Noneflow SupportDraw -> Noneflow + Empty -> Noneflow Tag _ -> Noneflow UsingDS _ -> Noneflow SwapAccrue -> Noneflow TxnDirection _ -> Noneflow BookLedgerBy _ _ -> Noneflow + TxnComments cmts -> --TODO the direction of combine txns let directionList = getFlow <$> cmts diff --git a/src/Types.hs b/src/Types.hs index 321bc8cc..aece56f3 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -36,7 +36,7 @@ module Types ,Txn(..),TxnComment(..) ,RoundingBy(..),DateDirection(..) ,BookDirection(..),IRR(..),DealCycle(..),Limit(..),Pre(..) - ,Liable(..),CumPrepay,CumDefault,CumDelinq,CumPrincipal,CumLoss,CumRecovery,PoolId(..) + ,CumPrepay,CumDefault,CumDelinq,CumPrincipal,CumLoss,CumRecovery,PoolId(..) ,DealName,lookupIntervalTable,CutoffFields(..),PriceResult(..) ,DueInt,DuePremium, DueIoI,DateVector,DealStats(..) ,PricingMethod(..),CustomDataType(..),ResultComponent(..),DealStatType(..) @@ -45,7 +45,7 @@ module Types ,DealStatRtn,Queryable(..) ,MyRatio,HowToPay(..),BondPricingMethod(..),InvestorAction(..) ,_BondTxn ,_InspectBal, _IrrResult,DueType(..) - ,EvalExpr(..),ErrorRep,Accruable(..),Payable(..),Drawable(..) + ,EvalExpr(..),ErrorRep ,SupportAvailType(..),updateSupportAvailType ,JRational) where @@ -893,25 +893,9 @@ getPriceValue x = error $ "failed to match with type when geting price value" + -- getValuation pr = error $ "not support for pricing result"++ show pr -class Liable lb where - -- must implement - isPaidOff :: lb -> Bool - getCurBalance :: lb -> Balance - getCurRate :: lb -> IRate - getOriginBalance :: lb -> Balance - getOriginDate :: lb -> Date - getAccrueBegDate :: lb -> Date - getDueInt :: lb -> Balance - getDueIntAt :: lb -> Int -> Balance - getDueIntOverInt :: lb -> Balance - getDueIntOverIntAt :: lb -> Int -> Balance - getTotalDueInt :: lb -> Balance - getTotalDueIntAt :: lb -> Int -> Balance - getOutstandingAmount :: lb -> Balance - -data DueType = DueInterest (Maybe Int) -- ^ interest due +data DueType = DueInterest (Maybe Int) -- ^ interest due | DuePrincipal -- ^ principal due | DueFee -- ^ fee due | DueResidual -- ^ residual @@ -920,35 +904,6 @@ data DueType = DueInterest (Maybe Int) -- ^ interest due deriving (Show, Eq, Generic) -class Accruable ac where - bookAccrual :: Date -> Balance -> ac -> ac - getAccrualDates :: Date -> ac -> [Date] - accrueTo :: Date -> ac -> ac - -- accrueWithDeal :: Date -> deal -> ac -> ac - -class Payable pa where - pay :: Date -> DueType -> Balance -> pa -> Either ErrorRep pa - getDueBal :: Date -> Maybe DueType -> pa -> Balance - writeOff :: Date -> DueType -> Amount -> pa -> Either ErrorRep pa - -class RateResettable rs where - getResetDates :: Date -> rs -> [Dates] - reset :: Date -> rs -> rs - -class Drawable dr where - draw :: Date -> Balance -> TxnComment -> dr -> Either ErrorRep dr - availForDraw :: Date -> dr -> SupportAvailType - - -- buildAccrualAction :: ac -> Date -> Date -> [ActionOnDate] - --- class Resettable rs where --- reset :: Date -> rs -> rs --- buildResetAction :: rs -> Date -> Date -> [Txn] - -class Collectable cl where - collect :: Date -> cl -> Either ErrorRep cl - availForCollect :: Date -> cl -> Either ErrorRep Balance - lookupTable :: Ord a => Table a b -> Direction -> (a -> Bool) -> Maybe b diff --git a/src/Waterfall.hs b/src/Waterfall.hs index 4cb387a4..ae05874b 100644 --- a/src/Waterfall.hs +++ b/src/Waterfall.hs @@ -81,7 +81,7 @@ data Action | PayFeeResidual (Maybe Limit) AccountName FeeName -- ^ pay fee regardless fee due amount -- Bond - Interest | CalcBondInt [BondName] - | CalcBondIntBy BondName DealStats DealStats -- ^ calculate interest due amount in the bond names,with optional balance and rate + | CalcBondIntBy BondName DealStats DealStats -- ^ calculate interest due amount in the bond names,with optional balance and rate | PayIntOverInt (Maybe Limit) AccountName [BondName] (Maybe ExtraSupport) -- ^ pay interest over interest only | PayInt (Maybe Limit) AccountName [BondName] (Maybe ExtraSupport) -- ^ pay interest with cash from the account with optional limit or extra support | PayIntAndBook (Maybe Limit) AccountName [BondName] (Maybe ExtraSupport) BookLedger -- ^ pay interest with cash from the account with optional limit or extra support @@ -142,6 +142,8 @@ data Action | ChangeStatus (Maybe Pre) DealStatus -- ^ change deal status -- Accrue Deal | AccrueDeal [String] + -- With Pay Group + | WithPay AccountName [ExtraSupport] deriving (Show,Generic,Eq,Ord) type DistributionSeq = [Action] diff --git a/swagger.json b/swagger.json index 1127aa4a..c090ae47 100644 --- a/swagger.json +++ b/swagger.json @@ -2035,6 +2035,38 @@ ], "title": "AccrueDeal", "type": "object" + }, + { + "properties": { + "contents": { + "items": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/components/schemas/ExtraSupport" + }, + "type": "array" + } + ], + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "tag": { + "enum": [ + "WithPay" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "title": "WithPay", + "type": "object" } ] }, From 2461365d55aeb51f1627bbdec46f501160443334 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Sun, 15 Mar 2026 23:03:49 +0800 Subject: [PATCH 15/17] fix UT --- test/UT/BondTest.hs | 1 + test/UT/ExpTest.hs | 1 + test/UT/UtilTest.hs | 1 + 3 files changed, 3 insertions(+) diff --git a/test/UT/BondTest.hs b/test/UT/BondTest.hs index dd48bb73..3460b0ef 100644 --- a/test/UT/BondTest.hs +++ b/test/UT/BondTest.hs @@ -19,6 +19,7 @@ import Data.Either import Util import Types import Data.Ratio +import Interface import Debug.Trace debug = flip trace diff --git a/test/UT/ExpTest.hs b/test/UT/ExpTest.hs index f18b80f2..c910df47 100644 --- a/test/UT/ExpTest.hs +++ b/test/UT/ExpTest.hs @@ -14,6 +14,7 @@ import qualified Deal.DealAction as DA import qualified UT.DealTest as DT import Expense import Types +import Interface import Deal.DealBase import qualified Cashflow as CF diff --git a/test/UT/UtilTest.hs b/test/UT/UtilTest.hs index 93d28450..d67a6c3d 100644 --- a/test/UT/UtilTest.hs +++ b/test/UT/UtilTest.hs @@ -16,6 +16,7 @@ import Util import DateUtil import Lib import Types +import Interface import Stmt import Data.Fixed import qualified Data.DList as DL From bcea3f5e5dd5748170b09b6e4d65ea6ac3219083 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Mon, 16 Mar 2026 18:33:27 +0800 Subject: [PATCH 16/17] expose AlwaysTrue/False --- src/Deal/DealQuery.hs | 4 +++- src/Types.hs | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Deal/DealQuery.hs b/src/Deal/DealQuery.hs index 038b9d87..6a099157 100644 --- a/src/Deal/DealQuery.hs +++ b/src/Deal/DealQuery.hs @@ -902,7 +902,9 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap Nothing -> Left $ "Date:"++show d++"Failed to query bool deal stat of -> "++ show s - + ConstTrue -> return True + ConstFalse -> return False + TestNot ds -> do not <$> (queryDealBool t rc ds d) TestAny b dss -> anyM (\ x -> (== b) <$> queryDealBool t rc x d ) dss TestAll b dss -> allM (\ x -> (== b) <$> queryDealBool t rc x d ) dss diff --git a/src/Types.hs b/src/Types.hs index aece56f3..f387c6e0 100644 --- a/src/Types.hs +++ b/src/Types.hs @@ -730,6 +730,8 @@ data DealStats = CurrentBondBalance | IsAnyOutstanding [BondName] | HasPassedMaturity [BondName] | TriggersStatus DealCycle String + | ConstTrue + | ConstFalse | DealStatBool DealStatFields -- rate type | PoolWaRate (Maybe PoolId) @@ -1195,6 +1197,8 @@ getDealStatType TestRate {} = RtnBool getDealStatType (TestAny _ _) = RtnBool getDealStatType (TestAll _ _) = RtnBool getDealStatType (DealStatBool _) = RtnBool +getDealStatType (ConstTrue) = RtnBool +getDealStatType (ConstFalse) = RtnBool getDealStatType (Max dss) = getDealStatType (head dss) getDealStatType (Min dss) = getDealStatType (head dss) From 09b9aeed5ceb8021b42f4c747c953aef99b8eb93 Mon Sep 17 00:00:00 2001 From: yellowbean Date: Tue, 17 Mar 2026 19:17:50 +0800 Subject: [PATCH 17/17] bump version to-> < 0.52.4 > --- CHANGELOG.md | 7 +++++++ Hastructure.cabal | 2 +- app/Main.hs | 2 +- swagger.json | 30 ++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a58f04e..4ecc3d7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,14 @@ +## 0.52.4 +### 2026-03-16 +* NEW: add `ConstTrue` / `ConstFalse` for bool type formula + + + ## 0.52.3 +### 2026-03-05 * NEW: add `IrrOfBond` as a formula, which return the irr of the bond * NEW: add `IsAnyOutstanding` as formula: return `True` if any of the bond is outstanding * NEW: add `PoolAccruedInterest` as a formula: return the accural amount of the pool diff --git a/Hastructure.cabal b/Hastructure.cabal index e6db210e..8b52cbd7 100644 --- a/Hastructure.cabal +++ b/Hastructure.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: Hastructure -version: 0.52.3 +version: 0.52.4 synopsis: Cashflow modeling library for structured finance description: Please see the README on GitHub at category: StructuredFinance,Securitisation,Cashflow diff --git a/app/Main.hs b/app/Main.hs index e5d6cb2b..ca1d3d9b 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -100,7 +100,7 @@ debug = flip Debug.Trace.trace version1 :: Version -version1 = Version "0.52.3" +version1 = Version "0.52.4" wrapRun :: [D.ExpectReturn] -> DealType -> Maybe AP.ApplyAssumptionType -> AP.NonPerfAssumption -> RunResp diff --git a/swagger.json b/swagger.json index c090ae47..1d7e77d3 100644 --- a/swagger.json +++ b/swagger.json @@ -8256,6 +8256,36 @@ "title": "TriggersStatus", "type": "object" }, + { + "properties": { + "tag": { + "enum": [ + "ConstTrue" + ], + "type": "string" + } + }, + "required": [ + "tag" + ], + "title": "ConstTrue", + "type": "object" + }, + { + "properties": { + "tag": { + "enum": [ + "ConstFalse" + ], + "type": "string" + } + }, + "required": [ + "tag" + ], + "title": "ConstFalse", + "type": "object" + }, { "properties": { "contents": {