Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2ea4a19
relax condition on account support ,with optional booking on ledger
yellowbean Dec 13, 2024
33d0afa
Fix Bnd stmt consol to expose rate
yellowbean Dec 14, 2024
8813d1b
BUGFIX: payPrinBySeq not working
yellowbean Dec 15, 2024
002afe6
Expose fund single bond
yellowbean Dec 15, 2024
b9f11fd
Expose bond return query type
yellowbean Dec 15, 2024
c3e136b
bump version to-> < 0.40.12 >
yellowbean Dec 15, 2024
8bc77e4
catch error when run look up rate function
yellowbean Dec 16, 2024
5abc4d3
WIP
yellowbean Dec 16, 2024
7d13763
Fix Assump Rate Error
yellowbean Dec 17, 2024
87aa615
Expose totalFunding on bonds
yellowbean Dec 17, 2024
79aa92c
bump version to-> < 0.40.13 >
yellowbean Dec 17, 2024
fcfa739
lift RateVector
yellowbean Dec 18, 2024
5d024f8
appendStmt arg switch
yellowbean Dec 18, 2024
01328b8
clean up
yellowbean Dec 18, 2024
c16e450
Expose BookTill/ Refactor RS hedge
yellowbean Dec 21, 2024
dbe45e0
FIX: only pay out Rate Swap Settlement if netcash is negative
yellowbean Dec 24, 2024
2696e52
add Daycount to RS; add PayIntAndBook;add SWAP txn name
yellowbean Dec 26, 2024
33c7ce8
WIP 2
yellowbean Dec 28, 2024
c17ebf2
Complete Sub Ordinated Interest
yellowbean Dec 31, 2024
c10a457
bump version to-> < 0.40.14 >
yellowbean Dec 31, 2024
b4e6188
expose formula in rateSwap
yellowbean Jan 2, 2025
1afe674
bump version to-> < 0.41.0 >
yellowbean Jan 2, 2025
9ed9184
expose AmountRequiredForIRR
yellowbean Jan 4, 2025
e6d4ec0
expose AmountRequiredForTargetIRR
yellowbean Jan 5, 2025
f4f321b
add deps: math-functions
yellowbean Jan 6, 2025
3e98a73
remove warehouse
yellowbean Jan 6, 2025
8c8aa8c
implment custom waterfall days
yellowbean Jan 11, 2025
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
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
<!-- towncrier release notes start -->


## 0.40.13
### 2024-12-17
* NEW: new formula `totalFunded` for bond with extra funding amount
* NEW: new deal run assumption `FundBond`, which records a time series funding amount for a single bond.
* ENHANCE: When booking account from `support` action, now user can book on `Credit` or `Debit` side
* FIX: `payPrinBySeq` was not working



## 0.40.9
### 2024-12-11
* ENHANCE: Ensure <limit> always return positive ,otherwise engine will throw error
* ENHANCE: Ensure `limit` always return positive ,otherwise engine will throw error
* NEW: add new action `changeStatus` in waterfall, with optional `Pre` as condition to trigger the status change


Expand Down
5 changes: 4 additions & 1 deletion Hastructure.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack

name: Hastructure
version: 0.40.11
version: 0.41.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 @@ -80,6 +80,7 @@ library
, hashable
, ieee754
, lens
, math-functions
, monad-loops
, numeric-limits
, openapi3
Expand Down Expand Up @@ -124,6 +125,7 @@ executable Hastructure-exe
, ieee754
, lens
, lucid
, math-functions
, monad-loops
, mtl
, numeric-limits
Expand Down Expand Up @@ -189,6 +191,7 @@ test-suite Hastructure-test
, hashable
, ieee754
, lens
, math-functions
, monad-loops
, numeric-limits
, openapi3
Expand Down
2 changes: 1 addition & 1 deletion app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ $(deriveJSON defaultOptions ''Version)
instance ToSchema Version

version1 :: Version
version1 = Version "0.40.11"
version1 = Version "0.41.0"



Expand Down
4 changes: 3 additions & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Hastructure
version: 0.40.11
version: 0.41.0
github: "yellowbean/Hastructure"
license: BSD3
author: "Xiaoyu"
Expand Down Expand Up @@ -47,6 +47,7 @@ dependencies:
- tabular
- numeric-limits
- scientific
- math-functions

library:
source-dirs:
Expand Down Expand Up @@ -86,6 +87,7 @@ executables:
- attoparsec
- exceptions
- tabular
- math-functions
# - servant-errors
# - servant-exceptions
- servant-checked-exceptions
Expand Down
55 changes: 21 additions & 34 deletions src/Accounts.hs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RankNTypes #-}

module Accounts (Account(..),ReserveAmount(..),draw,deposit
,transfer,depositInt
,InterestInfo(..),buildEarnIntAction,updateReserveBalance
,accBalLens,tryDraw,buildRateResetDates,accrueInt)
,transfer,depositInt ,InterestInfo(..),buildEarnIntAction
,accBalLens,tryDraw,buildRateResetDates,accrueInt,accTypeLens)
where
import qualified Data.Time as T
import Stmt (Statement(..),appendStmt,getTxnBegBalance,getDate
Expand All @@ -19,8 +19,9 @@ import Language.Haskell.TH
import Data.Aeson.TH
import Data.Aeson.Types
import GHC.Generics
import Control.Lens.Tuple

import Control.Lens
import Control.Lens hiding (Index)

import qualified InterestRate as IR

Expand All @@ -33,10 +34,6 @@ data InterestInfo = BankAccount IRate DatePattern Date
-- ^ float type: index, spread, sweep dates, rate reset , last accrue day, last reset rate
deriving (Show, Generic,Eq,Ord)


makePrisms ''InterestInfo


data ReserveAmount = PctReserve DealStats Rate -- ^ target amount with reference to % of formula
| FixReserve Balance -- ^ target amount with fixed balance amount
| Either Pre ReserveAmount ReserveAmount -- ^ target amount depends on a test, if true, then use first one ,otherwise use second one
Expand Down Expand Up @@ -68,15 +65,15 @@ accrueInt _ (Account _ _ Nothing _ _) = 0
-- ^ bank account type interest
accrueInt endDate a@(Account bal _ (Just interestType) _ stmt)
= case stmt of
Nothing -> mulBR (mulBI bal rateToUse) (yearCountFraction defaultDc lastDay endDate) -- `debug` (">>"++show lastCollectDate++">>"++show ed)
Just (Statement txns) ->
let
accrueTxns = sliceBy IE lastDay endDate txns
bals = map getTxnBegBalance accrueTxns ++ [bal]
ds = [lastDay] ++ getDates accrueTxns ++ [endDate]
avgBal = calcWeightBalanceByDates defaultDc bals ds
in
mulBI avgBal rateToUse
Nothing -> mulBR (mulBI bal rateToUse) (yearCountFraction defaultDc lastDay endDate) -- `debug` (">>"++show lastCollectDate++">>"++show ed)
Just (Statement txns) ->
let
accrueTxns = sliceBy IE lastDay endDate txns
bals = map getTxnBegBalance accrueTxns ++ [bal]
ds = [lastDay] ++ getDates accrueTxns ++ [endDate]
avgBal = calcWeightBalanceByDates defaultDc bals ds
in
mulBI avgBal rateToUse
where
defaultDc = DC_30E_360
(lastDay,rateToUse) = case interestType of
Expand All @@ -87,15 +84,14 @@ accrueInt endDate a@(Account bal _ (Just interestType) _ stmt)
depositInt :: Date -> Account -> Account
depositInt _ a@(Account _ _ Nothing _ _) = a
depositInt ed a@(Account bal _ (Just intType) _ stmt)
= a {accBalance = newBal ,accStmt= newStmt ,accInterest = Just (newIntInfoType intType)}
= a {accBalance = newBal ,accStmt= appendStmt newTxn stmt ,accInterest = Just (newIntInfoType intType)}
where
-- accruedInt = accrueInt a (mkTs [(lastCollectDate, toRational r),(ed, toRational r)]) ed
accruedInt = accrueInt ed a
newIntInfoType (BankAccount x y _d) = BankAccount x y ed
newIntInfoType (InvestmentAccount x y z z1 _d z2) = (InvestmentAccount x y z z1 ed z2)
newBal = accruedInt + bal -- `debug` ("INT ACC->"++ show accrued_int)
newTxn = AccTxn ed newBal accruedInt BankInt
newStmt = appendStmt stmt newTxn

-- | move cash from account A to account B
transfer :: (Account,Account) -> Date -> Amount -> (Account, Account)
Expand All @@ -107,16 +103,16 @@ transfer (sourceAcc@(Account sBal san _ _ sStmt), targetAcc@(Account tBal tan _
where
newSBal = sBal - amount
newTBal = tBal + amount
sourceNewStmt = appendStmt sStmt (AccTxn d newSBal (- amount) (Transfer san tan))
targetNewStmt = appendStmt tStmt (AccTxn d newTBal amount (Transfer san tan) )
sourceNewStmt = appendStmt (AccTxn d newSBal (- amount) (Transfer san tan)) sStmt
targetNewStmt = appendStmt (AccTxn d newTBal amount (Transfer san tan)) tStmt

-- | deposit cash to account with a comment
deposit :: Amount -> Date -> TxnComment -> Account -> Account
deposit amount d source acc@(Account bal _ _ _ maybeStmt) =
acc {accBalance = newBal, accStmt = newStmt}
where
newBal = bal + amount -- `debug` ("Date:"++show d++ "deposit"++show amount++"from"++show bal)
newStmt = appendStmt maybeStmt (AccTxn d newBal amount source)
newBal = bal + amount
newStmt = appendStmt (AccTxn d newBal amount source) maybeStmt

-- | draw cash from account with a comment
draw :: Amount -> Date -> TxnComment -> Account -> Account
Expand All @@ -131,20 +127,10 @@ tryDraw amt d tc acc@(Account bal _ _ _ maybeStmt)
| otherwise = ((0, amt), draw amt d tc acc)


-- | change reserve target info of account
updateReserveBalance :: ReserveAmount -> Account -> Account
updateReserveBalance ra acc = acc {accType = Just ra}

instance QueryByComment Account where
queryStmt (Account _ _ _ _ Nothing) tc = []
queryStmt (Account _ _ _ _ (Just (Statement txns))) tc = filter (\x -> getTxnComment x == tc) txns

-- | query total balance transfer from account a to account b
queryTrasnferBalance :: Account -> Account -> Balance
queryTrasnferBalance Account{accStmt = Nothing } Account{accName = an} = 0
queryTrasnferBalance a@Account{accName = fromAccName, accStmt = Just (Statement txns)} Account{accName = toAccName}
= sum $ getTxnAmt <$> queryStmt a (Transfer fromAccName toAccName)


-- InvestmentAccount Types.Index Spread DatePattern DatePattern Date IRate
buildRateResetDates :: Date -> Account -> Maybe (String,Dates)
Expand All @@ -154,7 +140,7 @@ buildRateResetDates _ _ = Nothing


makeLensesFor [("accBalance","accBalLens") ,("accName","accNameLens")
,("accType","accTypeLens") ,("accStmt","accStmtLens")] ''Account
,("accType","accTypeLens") ,("accStmt","accStmtLens"),("accInterest","accIntLens")] ''Account


instance IR.UseRate Account where
Expand All @@ -165,6 +151,7 @@ instance IR.UseRate Account where
getIndex _ = Nothing


makePrisms ''InterestInfo

$(deriveJSON defaultOptions ''InterestInfo)
$(deriveJSON defaultOptions ''ReserveAmount)
Expand Down
55 changes: 43 additions & 12 deletions src/Analytics.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Analytics (calcDuration,pv,calcWAL,pv2,pv3,fv2,pv21)
module Analytics (calcDuration,pv,calcWAL,pv2,pv3,fv2,pv21,calcRequiredAmtForIrrAtDate)

where
import Types
import Lib
Expand All @@ -15,6 +16,7 @@ import Data.Aeson.TH
import Data.Aeson.Types
import GHC.Generics
import Data.Ratio
import Numeric.RootFinding

import Debug.Trace
debug = flip trace
Expand All @@ -24,8 +26,8 @@ calcWAL :: TimeHorizion -> Balance -> Date -> [(Balance,Date)] -> Balance
calcWAL th bal d ps =
let
interval = case th of
ByYear -> 365
ByMonth -> 30
ByYear -> 365
ByMonth -> 30
weightedAmts = [ mulBR futureAmt ((daysBetween d futureDate) % interval) | (futureAmt,futureDate) <- ps ]
in
sum weightedAmts / bal
Expand All @@ -48,18 +50,21 @@ pv :: Ts -> Date -> Date -> Amount -> Amount
pv pc today d amt =
realToFrac $ (realToFrac amt) * (1 / factor) -- `debug` ("DF:"++show factor++" PV AMT"++show amt)
where
distance::Double = fromIntegral $ daysBetween today d
discount_rate = fromRational $ getValByDate pc Exc d -- `debug` ("Get val by ts"++show pc ++">>d"++ show d)
factor::Double = (1 + realToFrac discount_rate) ** (distance / 365) -- `debug` ("discount_rate"++show(discount_rate) ++" dist days=>"++show(distance))
distance::Double = fromIntegral $ daysBetween today d
discount_rate = fromRational $ getValByDate pc Exc d -- `debug` ("Get val by ts"++show pc ++">>d"++ show d)
factor::Double = (1 + realToFrac discount_rate) ** (distance / 365) -- `debug` ("discount_rate"++show(discount_rate) ++" dist days=>"++show(distance))

-- ^ calculate present value in the future using constant rate
pv2 :: IRate -> Date -> Date -> Amount -> Amount
pv2 discount_rate today d amt =
realToFrac $ (realToFrac amt) * (1/denominator) -- `debug` ("pv: cash"++ show amt++" deno"++ show denominator++">> rate"++show discount_rate)
where
denominator::Double = (1 + realToFrac discount_rate) ** (distance / 365)
distance::Double = fromIntegral $ daysBetween today d -- `debug` ("days betwwen"++ show (daysBetween today d)++">>"++ show d ++ ">>today>>"++ show today)
pv2 discount_rate today d amt
| today == d = amt
| otherwise
= realToFrac $ (realToFrac amt) * (1/denominator) -- `debug` ("pv: cash"++ show amt++" deno"++ show denominator++">> rate"++show discount_rate)
where
denominator::Double = (1 + realToFrac discount_rate) ** (distance / 365)
distance::Double = fromIntegral $ daysBetween today d -- `debug` ("days betwwen"++ show (daysBetween today d)++">>"++ show d ++ ">>today>>"++ show today)

-- ^ calculate present value to specific date given a series of amount with dates
pv21 :: IRate -> Date -> [Date] -> [Amount] -> Balance
pv21 r d ds vs = sum [ pv2 r d _d amt | (_d,amt) <- zip ds vs ]

Expand All @@ -78,4 +83,30 @@ fv2 discount_rate today futureDay amt
= realToFrac $ realToFrac amt * factor
where
factor::Double = (1 + realToFrac discount_rate) ** (distance / 365)
distance::Double = fromIntegral $ daysBetween today futureDay
distance::Double = fromIntegral $ daysBetween today futureDay


calcPvFromIRR :: Double -> [Date] -> [Amount] -> Date -> Double -> Double
calcPvFromIRR irr [] _ d amt = 0
calcPvFromIRR irr ds vs d amt =
let
begDate = head ds
pv = pv21 ((fromRational . toRational) irr) begDate (ds++[d]) (vs++[ (fromRational . toRational) amt ])
in
(fromRational . toRational) pv

-- IRR

-- ^ calculate IRR of a series of cashflow
calcRequiredAmtForIrrAtDate :: Double -> [Date] -> [Amount] -> Date -> Maybe Amount
calcRequiredAmtForIrrAtDate irr [] _ d = Nothing
calcRequiredAmtForIrrAtDate irr ds vs d =
let
def = RiddersParam
{ riddersMaxIter = 200
, riddersTol = RelTol 0.00000001
}
in
case ridders def (0.0001,100000000000000) (calcPvFromIRR irr ds vs d) of
Root finalAmt -> Just (fromRational (toRational finalAmt))
_ -> Nothing
Loading
Loading