Skip to content

Commit

Permalink
expose revolving dates/cfs (#140)
Browse files Browse the repository at this point in the history
* expose revolving dates/cfs
  • Loading branch information
yellowbean authored Dec 26, 2023
1 parent 83340bd commit 8c5d073
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 28 deletions.
34 changes: 19 additions & 15 deletions src/Cashflow.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module Cashflow (CashFlowFrame(..),Principals,Interests,Amount
import Data.Time (Day)
import Data.Fixed
import Lib (weightedBy,toDate,getIntervalFactors,daysBetween,paySeqLiabilitiesAmt)
import Util (mulBR,mulBInt,mulIR)
import Util (mulBR,mulBInt,mulIR,lastOf)
import DateUtil ( splitByDate )
import Types
import qualified Data.Map as Map
Expand Down Expand Up @@ -274,6 +274,7 @@ sumTs trs = tsSetDate (foldr1 addTs trs)

-- ^ group cashflow from same entity by a single date
sumTsCF :: [TsRow] -> Date -> TsRow
-- sumTsCF [] = tsSetDate (foldl1 addTsCF trs) -- `debug` ("Summing"++show trs++">>"++ show (tsSetDate (foldr1 addTsCF trs) d))
sumTsCF trs = tsSetDate (foldl1 addTsCF trs) -- `debug` ("Summing"++show trs++">>"++ show (tsSetDate (foldr1 addTsCF trs) d))

tsTotalCash :: TsRow -> Balance
Expand Down Expand Up @@ -377,6 +378,8 @@ tsReduceInt x (MortgageDelinqFlow _d a b c d e f g h i j k l) = MortgageDelinqFl
tsReduceInt x (MortgageFlow _d a b c d e f g h i j k) = MortgageFlow _d a b (c-x) d e f g h i j k
tsReduceInt x (LoanFlow _d a b c d e f g h i) = LoanFlow _d a b (c-x) d e f g h i


-- ^ claw back interest from cashflow records
clawbackInt :: Balance -> [TsRow] -> [TsRow]
clawbackInt bal txns
= let
Expand All @@ -402,33 +405,34 @@ aggregateTsByDate (r:rs) (tr:trs)

firstDate :: CashFlowFrame -> Date
firstDate (CashFlowFrame []) = error "empty cashflow frame to get first date"
firstDate (CashFlowFrame rs) = getDate $ head rs
firstDate (CashFlowFrame [r]) = getDate r
firstDate (CashFlowFrame (r:rs)) = getDate r

combine :: CashFlowFrame -> CashFlowFrame -> CashFlowFrame
combine (CashFlowFrame txn1) (CashFlowFrame txn2) = CashFlowFrame $ combineTss [] txn1 txn2

buildCollectedCF :: [[TsRow]] -> [Date] -> [TsRow] -> [[TsRow]]
buildCollectedCF [] [] [] = []
buildCollectedCF trs ds [] = trs
buildCollectedCF trs [d] _trs = trs ++ [cutBy Inc Past d _trs]
buildCollectedCF trs [] _trs = trs
buildCollectedCF trs ds [] = trs ++ [ [viewTsRow _d ((last . last) trs)] | _d <- ds ]
-- buildCollectedCF trs [d] _trs = trs ++ [cutBy Inc Past d _trs]
buildCollectedCF trs (d:ds) _trs =
case newFlow of
[] -> if null (last trs) then
buildCollectedCF (trs++[[]]) ds _trs
else
buildCollectedCF (trs++[[((viewTsRow d) . last .last) trs]]) ds _trs -- `debug` ("viewing trs"++ show trs)
-- [] -> if null (last trs) then
-- buildCollectedCF (trs++[[]]) ds _trs `debug` ("empty trs"++ show d)
-- else
-- buildCollectedCF (trs++[[(viewTsRow d . last .last) trs]]) ds _trs -- `debug` ("viewing trs"++ show trs)
[] -> case Util.lastOf trs (not . null) of
Nothing -> buildCollectedCF (trs++[[]]) ds _trs `debug` ("empty trs"++ show d)
Just lastTr -> buildCollectedCF (trs++[[viewTsRow d (last lastTr)]]) ds _trs `debug` ("non empty last tr "++ show lastTr ++ "for date"++ show d)
newFlow -> buildCollectedCF (trs++[newFlow]) ds remains
where
(newFlow, remains) = splitBy d Inc _trs
buildCollectedCF a b c = error $ "buildCollectedCF failed"++ show a++">>"++ show b++">>"++ show c

Check warning on line 431 in src/Cashflow.hs

View workflow job for this annotation

GitHub Actions / build

Pattern match is redundant

Check warning on line 431 in src/Cashflow.hs

View workflow job for this annotation

GitHub Actions / build

Pattern match is redundant


aggTsByDates :: [TsRow] -> [Date] -> [TsRow]
aggTsByDates trs ds =
map
(uncurry sumTsCF)
(filter
(\(y,__d) -> not (null y))
(zip (buildCollectedCF [] ds trs) ds))
aggTsByDates trs ds = uncurry sumTsCF <$> zip (buildCollectedCF [] ds trs) ds `debug` (">>> to sumTsCF "++ show (zip (buildCollectedCF [] ds trs) ds ))


mflowPrincipal :: TsRow -> Balance
Expand Down Expand Up @@ -566,7 +570,7 @@ emptyTsRow _d (FixedFlow a x c d e f ) = FixedFlow _d 0 0 0 0 0


viewTsRow :: Date -> TsRow -> TsRow
-- ^ take a snapshot of a record
-- ^ 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
Expand Down
25 changes: 17 additions & 8 deletions src/Deal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -477,15 +477,15 @@ priceBonds t@TestDeal {bonds = bndMap} (AP.RunZSpread curve bond_prices)
rateToday = getValByDate curve Inc

runDeal :: P.Asset a => TestDeal a -> ExpectReturn -> Maybe AP.ApplyAssumptionType-> AP.NonPerfAssumption
-> (TestDeal a,Maybe (Map.Map PoolId CF.CashFlowFrame), Maybe [ResultComponent],Maybe (Map.Map String L.PriceResult))
-> (TestDeal a, Maybe (Map.Map PoolId CF.CashFlowFrame), Maybe [ResultComponent], Maybe (Map.Map String L.PriceResult))
runDeal t _ perfAssumps nonPerfAssumps@AP.NonPerfAssumption{AP.callWhen = opts
,AP.pricing = mPricing
,AP.revolving = mRevolving
,AP.interest = mInterest}
| not runFlag = (t, Nothing, Just valLogs, Nothing)
| otherwise = (finalDeal, Just pcf, Just (getRunResult finalDeal ++ V.validateRun finalDeal ++logs), bndPricing) -- `debug` ("Run Deal end with")
| otherwise = (finalDeal, Just poolFlowUsedNoEmpty, Just (getRunResult finalDeal ++ V.validateRun finalDeal ++logs), bndPricing) -- `debug` ("Run Deal end with")
where
(runFlag,valLogs) = V.validateReq t nonPerfAssumps
(runFlag, valLogs) = V.validateReq t nonPerfAssumps
-- getinits() will get (new deal snapshot, actions, pool cashflows, unstressed pool cashflow)
(newT, ads, pcf, unStressPcf) = getInits t perfAssumps (Just nonPerfAssumps) -- `debug` ("runDeal init line")
-- extract Revolving Assumption
Expand All @@ -501,10 +501,12 @@ runDeal t _ perfAssumps [email protected]{AP.callWhen = opts
opts
mRevolvingCtx
[] -- `debug` ("start status"++show (status t) )-- `debug` ("run rAssump>>"++show revolvingAssump++"1st Action"++ show (head ads)++"PCF size"++show (CF.sizeCashFlowFrame pcf))
poolFlowUsed = Map.map (fromMaybe (CF.CashFlowFrame [])) (getAllCollectedFrame finalDeal Nothing)
poolFlowUsedNoEmpty = Map.map (over CF.cashflowTxn CF.dropTailEmptyTxns) poolFlowUsed
-- bond pricing if any
bndPricing = case mPricing of
Nothing -> Nothing -- `debug` ("pricing bpi with Nothing")
Just _bpi -> Just (priceBonds finalDeal _bpi) -- `debug` ("Pricing with")
Just _bpi -> Just (priceBonds finalDeal _bpi)

-- | get bond principal and interest shortfalls from a deal
getRunResult :: TestDeal a -> [ResultComponent]
Expand All @@ -514,9 +516,16 @@ getRunResult t = os_bn_i ++ os_bn_b
os_bn_b = [ BondOutstanding (L.bndName _b) (L.bndBalance _b) (getBondBegBal t (L.bndName _b)) | _b <- bs ]
os_bn_i = [ BondOutstandingInt (L.bndName _b) (L.bndDueInt _b) (getBondBegBal t (L.bndName _b)) | _b <- bs ]

prepareDeal :: TestDeal a -> TestDeal a
prepareDeal :: P.Asset a => TestDeal a -> TestDeal a
prepareDeal t@TestDeal {bonds = bndMap}
= t {bonds = Map.map L.consolStmt bndMap} -- `debug` ("Consolidation in Preparing")
= let
ptMap = view (dealPool . poolTypePool) t
newPtMap = Map.map (over P.poolFutureCf
(\mCf -> (over CF.cashflowTxn CF.dropTailEmptyTxns) <$> mCf ))
ptMap
t1 = set (dealPool . poolTypePool) newPtMap t
in
t1 {bonds = Map.map L.consolStmt bndMap} -- `debug` ("Consolidation in Preparing")

appendCollectedCF :: P.Asset a => Date -> TestDeal a -> Map.Map PoolId CF.CashFlowFrame -> TestDeal a
-- ^ append cashflow frame (consolidate by a date) into deals collected pool
Expand Down Expand Up @@ -735,9 +744,9 @@ getInits t@TestDeal{fees=feeMap,pool=thePool,status=status,bonds=bndMap} mAssump
poolCfTsM = Map.map (\x -> cutBy Inc Future startDate (CF.getTsCashFlowFrame (fst x))) pCfM -- `debug` ("proj>>>>> "++ show pCfM)
-- poolAggCf = CF.aggTsByDates poolCfTs (getDates pActionDates)
poolCfTsMwithBegRow = Map.map (\(x:xs) -> buildBegTsRow startDate x:x:xs) poolCfTsM --TODO what if txn is empty ??
poolAggCfM = Map.map (\x -> CF.aggTsByDates x (getDates pActionDates)) poolCfTsMwithBegRow
poolAggCfM = Map.map (\x -> CF.aggTsByDates x (getDates pActionDates)) poolCfTsMwithBegRow --`debug` ("pool action dates"++ show (getDates pActionDates))
-- pCollectionCfAfterCutoff = CF.CashFlowFrame $ begRow:poolAggCf
pCollectionCfAfterCutoff = Map.map CF.CashFlowFrame poolAggCfM
pCollectionCfAfterCutoff = Map.map CF.CashFlowFrame poolAggCfM -- `debug` ("pool Agg Cfm"++ show poolAggCfM)

pScheduleCfM = case thePool of
(SoloPool p)
Expand Down
8 changes: 7 additions & 1 deletion src/Deal/DealAction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,13 @@ performActionWrap d
newPcf = let
pIdToChange = fromMaybe PoolConsol pId
in
Map.adjust (\(CF.CashFlowFrame trs) -> CF.CashFlowFrame (CF.combineTss [] trs newBoughtTxn)) pIdToChange pFlowMap -- `debug` ("date"++show d ++">>Asset bought txn"++ show newBoughtTxn)
Map.adjust (\(CF.CashFlowFrame trs) ->
let
dsInterval = getDate <$> trs -- `debug` (">>> agg interval : "++ show (getDate <$> trs ))
in
CF.CashFlowFrame $ CF.aggTsByDates (CF.combineTss [] trs newBoughtTxn) dsInterval)
pIdToChange
pFlowMap -- `debug` ("date"++show d ++">>Asset bought txn"++ show newBoughtTxn)
newRc = rc {runPoolFlow = newPcf
,revolvingAssump = Just (poolAfterBought, perfAssumps)}

Expand Down
12 changes: 11 additions & 1 deletion src/Deal/DealBase.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
module Deal.DealBase (TestDeal(..),SPV(..),dealBonds,dealFees,dealAccounts,dealPool,PoolType(..),getIssuanceStats
,getAllAsset,getAllAssetList,getAllCollectedFrame,getLatestCollectFrame,getAllCollectedTxns
,getIssuanceStatsConsol,getAllCollectedTxnsList,getPoolsByName,getScheduledCashflow,dealScheduledCashflow
,getPoolIds)
,getPoolIds,poolTypePool)
where
import qualified Accounts as A
import qualified Ledger as LD
Expand Down Expand Up @@ -65,6 +65,16 @@ data PoolType a = SoloPool (P.Pool a)
| ResecDeal (Map.Map (BondName, Rate) (TestDeal a))
deriving (Generic,Eq,Show,Ord)

poolTypePool :: P.Asset a => Lens' (PoolType a) (Map.Map PoolId (P.Pool a))
poolTypePool = lens getter setter
where
getter (SoloPool p) = Map.fromList [(PoolConsol,p)]
getter (MultiPool pm) = pm
setter (SoloPool p) newPool = case Map.lookup PoolConsol newPool of
Just p -> SoloPool p
Nothing -> error $ "Can't set a solo pool to a multi pool"
setter (MultiPool pm) newPool = MultiPool newPool


data TestDeal a = TestDeal { name :: String
,status :: DealStatus
Expand Down
12 changes: 12 additions & 0 deletions src/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Util
,shiftTsByAmt,calcWeightBalanceByDates, monthsAfter
,getPriceValue,maximum',minimum',roundingBy,roundingByM
,floorWith,slice,toPeriodRateByInterval, dropLastN
,lastOf
-- for debug
,zyj
)
Expand Down Expand Up @@ -210,6 +211,17 @@ daysInterval ds = zipWith daysBetween (init ds) (tail ds)
debugLine :: Show a => [a] -> String
debugLine xs = ""

lastOf:: [a] -> (a->Bool) -> Maybe a
lastOf [] fn = Nothing
lastOf xs fn =
let
l = last xs
in
if fn l then
Just l
else
lastOf (init xs) fn

shiftTsByAmt :: Ts -> Rational -> Ts
shiftTsByAmt (IRateCurve tps) delta
= IRateCurve $ [ TsPoint d (fromRational delta+v) | TsPoint d v <- tps ]
Expand Down
2 changes: 1 addition & 1 deletion swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -14327,7 +14327,7 @@
}
},
"info": {
"version": "0.24.2",
"version": "0.24.3",
"title": "Hastructure API",
"license": {
"name": "BSD 3"
Expand Down
2 changes: 2 additions & 0 deletions test/MainTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import qualified Data.Vector as UtilT
import qualified UT.AnalyticsTest as AnalyticsT
import qualified UT.UtilTest as RH
import qualified UT.RateHedgeTest as RHT
import GHC.Generics (U1(U1))

main = defaultMain tests

Expand Down Expand Up @@ -78,6 +79,7 @@ tests = testGroup "Tests" [AT.mortgageTests
,UtilT.sliceTest
,UtilT.splitTsTest
,UtilT.tableTest
,UtilT.lastOftest
,AccT.intTests
,AccT.investTests
,AccT.reserveAccTest
Expand Down
4 changes: 3 additions & 1 deletion test/UT/CashflowTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ cfTests = testGroup "Cashflow Utils"
,testCase "Cashflow Aggregation" $
assertEqual "aggregate period with no cf"
[CF.MortgageFlow (L.toDate "20220101") 100 10 10 0 0 0 0 0 Nothing Nothing Nothing
,CF.MortgageFlow (L.toDate "20220102") 100 0 0 0 0 0 0 0 Nothing Nothing Nothing]
,CF.MortgageFlow (L.toDate "20220102") 100 0 0 0 0 0 0 0 Nothing Nothing Nothing
,CF.MortgageFlow (L.toDate "20220111") 100 0 0 0 0 0 0 0 Nothing Nothing Nothing
]
(CF.aggTsByDates trs (L.toDates ["20220101","20220102","20220111"]))

,testCase "Get Latest Cashflow 1" $
Expand Down
14 changes: 13 additions & 1 deletion test/UT/UtilTest.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module UT.UtilTest(daycountTests1,daycountTests2,daycountTests3,daycountTests4
,tsTest,ts2Test,ts3Test,dateVectorPatternTest,paddingTest,dateSliceTest
,capTest,roundingTest,sliceTest,splitTsTest,tableTest)--,daycountTests3,daycountTests4)
,capTest,roundingTest,sliceTest,splitTsTest,tableTest,lastOftest)--,daycountTests3,daycountTests4)
where

import Test.Tasty
Expand Down Expand Up @@ -532,3 +532,15 @@ tableTest =
[Just 400,Just 300,Just 200,Nothing]
[lookupTable tbl Up (20 >=),lookupTable tbl Up (16 >=),lookupTable tbl Up (11 >=),lookupTable tbl Up (3 >=) ]
]

lastOftest =
let
a = [1,2,3,4,5]
b = [[1],[3],[],[5],[],[]]
in
testGroup "test on last of on list" [
testCase "on non empty" $
assertEqual "should be [5]"
(Just [5])
(Util.lastOf b (not . null))
]

0 comments on commit 8c5d073

Please sign in to comment.