Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@

<!-- towncrier release notes start -->


## 0.45.1
### 2025-03-25
* BREAK:
* FIX: in `Pricing/IRR`, error when holding position is too small
* NEW:
* ENHANCE:
* ENHANCE: engine will auto patch `interest start date` for bonds if it is not modeled. In `PreClosing` status, engine will use `closing date` as bond interest begin date ; In `Non-PreClosing` status, it defaults to use last waterfall distribution date as bond interest begin date.


## 0.45.0
### 2025-03-21
* BREAK: remove unused `DealDates` : `FixInterval`, `CustomDates` and `PatternInterval`. Since all these can be replace by new `GenericDates` in type `DateDesp`
* ENHANCE: now bond with `No last interest accure day` will begin accrue interest from `closing date` if the deal is in `PreClosing` mode, while the bond will use `last bond day` otherwise.
* FIX: `IsPaidOff` now can be queried in inspection formula





## 0.44.0
Expand Down
13 changes: 8 additions & 5 deletions Hastructure.cabal
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
cabal-version: 1.12

-- This file has been generated from package.yaml by hpack version 0.35.2.
-- This file has been generated from package.yaml by hpack version 0.37.0.
--
-- see: https://github.com/sol/hpack

name: Hastructure
version: 0.41.3
version: 0.45.0
description: Please see the README on GitHub at <https://github.com/yellowbean/Hastructure#readme>
category: StructuredFinance;Securitisation;Cashflow
homepage: https://github.com/yellowbean/Hastructure#readme
Expand Down Expand Up @@ -73,7 +73,8 @@ library
hs-source-dirs:
src
build-depends:
aeson
Decimal
, aeson
, aeson-pretty
, base
, bytestring
Expand Down Expand Up @@ -117,7 +118,8 @@ executable Hastructure-exe
app
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
Hastructure
Decimal
, Hastructure
, aeson
, aeson-pretty
, attoparsec
Expand Down Expand Up @@ -191,7 +193,8 @@ test-suite Hastructure-test
test
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
Hastructure
Decimal
, Hastructure
, aeson
, aeson-pretty
, base
Expand Down
1 change: 1 addition & 0 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ debug = flip Debug.Trace.trace
version1 :: Version
version1 = Version "0.45.1"


wrapRun :: DealType -> Maybe AP.ApplyAssumptionType -> AP.NonPerfAssumption -> RunResp
wrapRun (MDeal d) mAssump mNonPerfAssump
= do
Expand Down
3 changes: 2 additions & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Hastructure
version: 0.41.3
version: 0.45.0
github: "yellowbean/Hastructure"
license: BSD3
author: "Xiaoyu"
Expand All @@ -21,6 +21,7 @@ description: Please see the README on GitHub at <https://github.com/yell

dependencies:
- base
- Decimal
- hashable
- time
- lens
Expand Down
14 changes: 8 additions & 6 deletions src/Deal/DealAction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,15 @@ calcDueInt t d b@(L.BondGroup bMap pt)
m <- mapM (calcDueInt t d) bMap
return $ L.BondGroup m pt

-- won't accrue interest
calcDueInt t d b@(L.Bond _ bt oi io _ bal r dp _ di Nothing _ lastPrinPay _ )
| d <= closingDate = Right b
-- first time to accrue interest\
-- use default date to start to accrue
calcDueInt t@TestDeal{ status = st} d b@(L.Bond _ bt oi io _ bal r dp _ di Nothing _ _ _ )
| bal+di == 0 && (bt /= L.IO) = Right b
| otherwise = calcDueInt t d (b {L.bndDueIntDate = Just closingDate }) -- `debug` ("hit")
where
closingDate = getClosingDate (dates t)
| otherwise =
do
sd <- getClosingDate (dates t)
b' <- calcDueInt t d (b {L.bndDueIntDate = Just sd }) -- `debug` ("hit")
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) _ _ _ )
Expand Down
26 changes: 2 additions & 24 deletions src/Deal/DealBase.hs
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,8 @@ type StatedDate = Date
type DistributionDates = DatePattern
type PoolCollectionDates = DatePattern

data DateDesp = FixInterval (Map.Map DateType Date) Period Period
| CustomDates CutoffDate [ActionOnDate] ClosingDate [ActionOnDate]
| PatternInterval (Map.Map DateType (Date, DatePattern, Date))
-- cutoff closing mRevolving end-date dp1-pc dp2-bond-pay
| PreClosingDates CutoffDate ClosingDate (Maybe RevolvingDate) StatedDate (Date,PoolCollectionDates) (Date,DistributionDates)

data DateDesp = PreClosingDates CutoffDate ClosingDate (Maybe RevolvingDate) StatedDate (Date,PoolCollectionDates) (Date,DistributionDates)
-- <Pool Collection DP> <Waterfall DP>
-- (last collect,last pay), mRevolving end-date dp1-pool-pay dp2-bond-pay
| CurrentDates (Date,Date) (Maybe Date) StatedDate (Date,PoolCollectionDates) (Date,DistributionDates)
Expand All @@ -195,25 +192,6 @@ data DateDesp = FixInterval (Map.Map DateType Date) Period Period


populateDealDates :: DateDesp -> DealStatus -> Either String (Date,Date,Date,[ActionOnDate],[ActionOnDate],Date,[ActionOnDate])
populateDealDates (CustomDates cutoff pa closing ba) _
= Right $
(cutoff
,closing
,getDate (head ba)
,pa
,ba
,getDate (max (last pa) (last ba))
,[])

populateDealDates (PatternInterval _m) _
= Right (cutoff,closing,nextPay,pa,ba,max ed1 ed2, [])
where
(cutoff,dp1,ed1) = _m Map.! CutoffDate
(nextPay,dp2,ed2) = _m Map.! FirstPayDate
(closing,_,_) = _m Map.! ClosingDate
pa = [ PoolCollection _d "" | _d <- genSerialDatesTill cutoff dp1 ed1 ]
ba = [ RunWaterfall _d "" | _d <- genSerialDatesTill nextPay dp2 ed2 ]

populateDealDates (PreClosingDates cutoff closing mRevolving end (firstCollect,poolDp) (firstPay,bondDp)) _
= Right (cutoff,closing,firstPay,pa,ba,end, [])
where
Expand Down
39 changes: 13 additions & 26 deletions src/Deal/DealDate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}

module Deal.DealDate (DealDates,getClosingDate,getFirstPayDate)
module Deal.DealDate (DealDates,getClosingDate,getFirstPayDate,getLastPayDate)
where

import qualified Data.Map as Map
Expand All @@ -12,40 +12,27 @@ import Types
import Lib

class DealDates a where
getClosingDate :: a -> Date
getClosingDate :: a -> Either String Date
getFirstPayDate :: a -> Date
getLastPayDate :: a -> Either String Date

instance DealDates DateDesp where
getClosingDate (PatternInterval _m)
= let
(sd,dp,ed) = _m Map.! ClosingDate
in
sd

getClosingDate (CustomDates _ _ cd _) = cd

getClosingDate (GenericDates m) = case Map.lookup ClosingDate m of
Just (SingletonDate x) -> x
Nothing -> error "ClosingDate not found in GenericDates"
Just (SingletonDate x) -> Right x
Nothing -> Left $ "ClosingDate not found in GenericDates"++show m

getClosingDate (FixInterval _m _p1 _p2) = _m Map.! ClosingDate
getClosingDate (PreClosingDates _ x _ _ _ _) = Right x

getClosingDate (PreClosingDates _ x _ _ _ _) = x
getClosingDate (CurrentDates (_,cd) _ _ _ _ ) = Right cd

getClosingDate (CurrentDates (_,cd) _ _ _ _ ) = cd
getLastPayDate (GenericDates m) = case Map.lookup LastPayDate m of
Just (SingletonDate x) -> Right x
Nothing -> Left $ "LastPayDate not found in GenericDates"++ show m

getFirstPayDate (PatternInterval _m)
= let
(sd,dp,ed) = _m Map.! FirstPayDate
in
sd

getFirstPayDate (CustomDates _ _ _ bActions )
= getDate $ head bActions

getFirstPayDate (FixInterval _m _p1 _p2)
= _m Map.! FirstPayDate
getLastPayDate (CurrentDates (_,cd) _ _ _ _ ) = Right cd

getLastPayDate (PreClosingDates {}) = Left "Error : try to get last pay date from PreClosingDates"

getFirstPayDate (PreClosingDates _ _ _ _ _ (fp,_)) = fp

getFirstPayDate (CurrentDates _ _ _ _ (cpay,_)) = cpay
Expand Down
17 changes: 10 additions & 7 deletions src/Deal/DealQuery.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ calcBondTargetBalance t d (L.BondGroup bMap mPt) =

Just (L.PAC _target) -> Right $ getValOnByDate _target d
Just (L.PacAnchor _target _bnds)
| queryDealBool t (IsPaidOff _bnds) d == Right True -> Right $ sum $ L.getCurBalance <$> Map.elems bMap
| queryDealBool t (IsPaidOff _bnds) d == Right False -> Right $ getValOnByDate _target d
| queryDealBool t (IsPaidOff _bnds) d == Right True ->
do
subBondTargets <- sequenceA $ calcBondTargetBalance t d <$> Map.elems bMap
return $ sum subBondTargets
| queryDealBool t (IsPaidOff _bnds) d == Right False -> Right $ 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 -> Right v
Expand All @@ -95,8 +98,8 @@ calcBondTargetBalance t d b =
L.Equity -> Right 0
L.PAC _target -> Right $ getValOnByDate _target d
L.PacAnchor _target _bnds
| queryDealBool t (IsPaidOff _bnds) d == Right True -> Right $ L.getCurBalance b
| queryDealBool t (IsPaidOff _bnds) d == Right False -> Right $ getValOnByDate _target d
| queryDealBool t (IsPaidOff _bnds) d == Right True -> Right $ 0
| queryDealBool t (IsPaidOff _bnds) d == Right False -> Right $ 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 -> Right v
Expand Down Expand Up @@ -761,7 +764,7 @@ queryCompound t@TestDeal{accounts=accMap, bonds=bndMap, ledgers=ledgersM, fees=f
Just v -> Right . toRational $ v
Nothing -> Left $ "Date:"++show d++"Failed to query balance deal stat of -> "++ show s

_ -> Left ("Date:"++show d++"Failed to query formula of -> "++ show s)
_ -> Left ("Date:"++show d++"Failed to query balance formula of -> "++ show s)



Expand Down Expand Up @@ -793,8 +796,8 @@ queryDealBool t@TestDeal{triggers= trgs,bonds = bndMap,fees= feeMap

IsPaidOff bns ->
do
vs <- lookupAndApplies isPaidOff "Is Paid Off" bns bndMap
return $ and vs
vs <- lookupAndApplies isPaidOff "Is Paid Off" bns bndMap
return $ and vs -- `debug` ("bond paid off?"++ show vs)

IsOutstanding bns ->
do
Expand Down
79 changes: 41 additions & 38 deletions src/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import Data.Aeson.TH
import Data.Aeson.Types
import Data.Fixed hiding (Ratio)
import Data.Decimal
import Data.Ix


Expand All @@ -86,55 +87,58 @@
type AccountName = String
type AccNames = [String]
type CeName = String
type Balance = Centi
type Comment = String

type Date = Time.Day
type Dates = [Time.Day]
type StartDate = Date
type EndDate = Date
type LastIntPayDate = Date

type Balance = Centi
-- type Balance = Decimal
type Amount = Balance
type Principal = Balance
type Valuation = Balance

type Interest = Balance
type Default = Balance
type Loss = Balance
type Cash = Balance
type Recovery = Balance
type Prepayment = Balance
type Rental = Balance
type PrepaymentPenalty = Balance
type CumPrepay = Balance
type CumPrincipal = Balance
type CumDefault = Balance
type CumDelinq = Balance
type CumLoss = Balance
type CumRecovery = Balance
type AccruedInterest = Balance

type PerFace = Micro
type WAL = Centi
type Duration = Micro
type Convexity = Micro
type Yield = Micro
type IRR = Micro

type Rate = Rational -- general Rate like pool factor
type PrepaymentRate = Rate
type DefaultRate = Rate
type RecoveryRate = Rate

type IRate = Micro -- Interest Rate Type
type Spread = Micro
type Amount = Centi
type Comment = String
type StartDate = Time.Day
type EndDate = Time.Day
type LastIntPayDate = Time.Day
type Floor = Micro
type Principal = Centi
type Interest = Centi
type Default = Centi
type Loss = Centi
type Cash = Centi
type Recovery = Centi
type Prepayment = Centi
type Rental = Centi
type Cap = Micro
type PrepaymentPenalty = Centi

type CumPrepay = Centi
type CumPrincipal = Centi
type CumDefault = Centi
type CumDelinq = Centi
type CumLoss = Centi
type CumRecovery = Centi

type PrepaymentRate = Rate
type DefaultRate = Rate
type RecoveryRate = Rate
type RemainTerms = Int
type BorrowerNum = Int
type Lag = Int


type Valuation = Centi
type PerFace = Micro
type WAL = Centi
type Duration = Micro
type Convexity = Micro
type Yield = Micro
type AccruedInterest = Centi
type IRR = Micro


data Index = LPR5Y
| LPR1Y
| LIBOR1M
Expand Down Expand Up @@ -898,6 +902,7 @@
_ -> error $ "Invalid PoolId: "++ show pn


$(deriveJSON defaultOptions ''DecimalRaw)
$(deriveJSON defaultOptions ''TsPoint)
$(deriveJSON defaultOptions ''PerPoint)
$(deriveJSON defaultOptions ''Ts)
Expand Down Expand Up @@ -981,7 +986,7 @@
toJSON (PayGroupInt bns) = String $ T.pack $ "<PayGroupInt:"++ listToStrWithComma bns ++ ">"
toJSON (PayGroupPrin bns) = String $ T.pack $ "<PayGroupPrin:"++ listToStrWithComma bns ++ ">"
toJSON (BookLedgerBy dr lName) = String $ T.pack $ "<BookLedger:"++ lName ++ ">"
toJSON x = error $ "Not support for toJSON for "++show x

Check warning on line 989 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

Pattern match is redundant

Check warning on line 989 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

Pattern match is redundant

instance FromJSON TxnComment where
parseJSON = withText "Empty" parseTxn
Expand All @@ -993,7 +998,7 @@
"Transfer" -> let
sv = T.splitOn (T.pack ",") $ T.pack contents
in
return $ Transfer (T.unpack (head sv)) (T.unpack (sv!!1))

Check warning on line 1001 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1001 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
"Support" -> return $ LiquidationSupport contents
"PayInt" -> return $ PayInt [contents]
"PayYield" -> return $ PayYield contents
Expand All @@ -1001,7 +1006,7 @@
"WriteOff" -> let
sv = T.splitOn (T.pack ",") $ T.pack contents
in
return $ WriteOff (T.unpack (head sv)) (read (T.unpack (sv!!1))::Balance)

Check warning on line 1009 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1009 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
"PayPrinResidual" -> return $ PayPrinResidual [contents]
"PayFee" -> return $ PayFee contents
"SeqPayFee" -> return $ SeqPayFee [contents]
Expand All @@ -1009,10 +1014,10 @@
"TransferBy" -> let
sv = T.splitOn (T.pack ",") $ T.pack contents
in
return $ TransferBy (T.unpack (head sv)) (T.unpack (sv!!1)) (read (T.unpack (sv!!2))::Limit)

Check warning on line 1017 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1017 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
"Pool" -> let
sr = T.splitOn (T.pack ":") $ T.pack contents
mPids = if head sr == "Nothing" then

Check warning on line 1020 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1020 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
Nothing
else
Just (read <$> T.unpack <$> sr)::(Maybe [PoolId])
Expand All @@ -1028,7 +1033,7 @@
"LiquidationSupportExp" -> let
sv = T.splitOn (T.pack ",") $ T.pack contents
in
return $ LiquidationSupportInt (read (T.unpack (head sv))::Balance) (read (T.unpack (sv!!1))::Balance)

Check warning on line 1036 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1036 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
"SupportDraw" -> return SupportDraw
"Draw" -> return LiquidationDraw
"Repay" -> return $ LiquidationRepay contents
Expand All @@ -1044,15 +1049,15 @@
"FundWith" -> let
sv = T.splitOn (T.pack ",") $ T.pack contents
in
return $ FundWith (T.unpack (head sv)) (read (T.unpack (sv!!1))::Balance)

Check warning on line 1052 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1052 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
-- toJSON (IssuanceProceeds nb) = String $ T.pack $ "<IssuanceProceeds:"++nb++">"
"IssuanceProceeds" -> return $ IssuanceProceeds contents
"Tag" -> return $ Tag contents
where
pat = "<(\\S+):(\\S+)>"::String
sr = (T.unpack t =~ pat)::[[String]]
tagName = head sr!!1::String

Check warning on line 1059 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1059 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
contents = head sr!!2::String

Check warning on line 1060 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1060 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’


data DealStatType = RtnBalance
Expand Down Expand Up @@ -1087,6 +1092,7 @@
getDealStatType (DealStatInt _) = RtnInt

getDealStatType (IsMostSenior _ _) = RtnBool
getDealStatType (IsPaidOff {}) = RtnBool
getDealStatType (TriggersStatus _ _)= RtnBool
getDealStatType (IsDealStatus _)= RtnBool
getDealStatType TestRate {} = RtnBool
Expand All @@ -1094,7 +1100,7 @@
getDealStatType (TestAll _ _) = RtnBool
getDealStatType (DealStatBool _) = RtnBool

getDealStatType (Max dss) = getDealStatType (head dss)

Check warning on line 1103 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’

Check warning on line 1103 in src/Types.hs

View workflow job for this annotation

GitHub Actions / build

In the use of ‘head’
getDealStatType (Min dss) = getDealStatType (head dss)
getDealStatType _ = RtnBalance

Expand All @@ -1110,12 +1116,9 @@


$(deriveJSON defaultOptions ''BondPricingMethod)

$(deriveJSON defaultOptions ''DealStatus)
$(deriveJSON defaultOptions ''CutoffType)

$(deriveJSON defaultOptions ''DealStatFields)

$(concat <$> traverse (deriveJSON defaultOptions) [''BookDirection, ''DealStats, ''PricingMethod, ''DealCycle, ''DateType, ''Period,
''DatePattern, ''Table, ''BalanceSheetReport, ''BookItem, ''CashflowReport, ''Txn] )

Expand Down
Loading
Loading