packages feed

Hastructure-0.45.0: test/UT/BondTest.hs

module UT.BondTest(pricingTests,bndConsolTest,writeOffTest)
where

import Test.Tasty
import Test.Tasty.HUnit

import qualified Data.Time as T
import qualified Liability as B
import qualified Deal as D
import qualified Lib as L
import qualified Stmt  as S
import qualified Asset as P
import qualified Assumptions as A
import qualified Cashflow as CF
import qualified Data.DList as DL
import Util
import Types
import Data.Ratio

import Debug.Trace
debug = flip trace

b1Txn =  DL.fromList [ BondTxn (L.toDate "20220501") 1500 10 500 0.08 510 0 0 Nothing S.Empty
          ,BondTxn (L.toDate "20220801") 0 10 1500 0.08 1510 0 0 Nothing S.Empty ]
b1 = B.Bond{B.bndName="A"
            ,B.bndType=B.Sequential
            ,B.bndOriginInfo= B.OriginalInfo{
                               B.originBalance=3000
                               ,B.originDate= T.fromGregorian 2021 1 1
                               ,B.originRate= 0.08
                               ,B.maturityDate = Nothing}
            ,B.bndInterestInfo= B.Fix 0.08 DC_ACT_365F
            ,B.bndBalance=3000
            ,B.bndRate=0.08
            ,B.bndDuePrin=0.0
            ,B.bndStepUp = Nothing
            ,B.bndDueInt=0.0
            ,B.bndDueIntOverInt=0.0
            ,B.bndDueIntDate=Nothing
            ,B.bndLastIntPay = Just (T.fromGregorian 2021 1 1)
            ,B.bndLastPrinPay = Just (T.fromGregorian 2022 1 1)
            ,B.bndStmt=Just (S.Statement b1Txn)}

bfloat = B.Bond{B.bndName="A"
            ,B.bndType=B.Sequential
            ,B.bndOriginInfo= B.OriginalInfo{
                               B.originBalance=3000
                               ,B.originDate= T.fromGregorian 2022 1 1
                               ,B.originRate= 0.08
                               ,B.maturityDate = Nothing}
            ,B.bndInterestInfo= B.Floater 0.02 LPR5Y 0.015 (MonthDayOfYear 1 1) DC_ACT_365F Nothing Nothing
            ,B.bndBalance=3000
            ,B.bndRate=0.08
            ,B.bndStepUp = Nothing
            ,B.bndDuePrin=0.0
            ,B.bndDueInt=0.0
            ,B.bndDueIntDate=Nothing
            ,B.bndDueIntOverInt=0.0
            ,B.bndLastIntPay = Just (T.fromGregorian 2022 1 1)
            ,B.bndLastPrinPay = Just (T.fromGregorian 2022 1 1)
            ,B.bndStmt=Just $ S.Statement (DL.fromList [ BondTxn (L.toDate "20220501") 1500 10 500 0.08 510 0 0 Nothing S.Empty])}


pricingTests = testGroup "Pricing Tests"
  [
    let
      _ts = L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.05, L.TsPoint (L.toDate "20240101") 0.05]
      _pv_day = L.toDate "20220201"
      _f_day = L.toDate "20230201"
      _pv = B.pv _ts _pv_day _f_day 103
    in
      testCase "PV test" $
        assertEqual "simple PV with flat curve"  
          98.09
          _pv,
    let
        _pv_day = L.toDate "20220201"
        _f_day = L.toDate "20230201"
        _ts1 = L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.01, L.TsPoint (L.toDate "20230101") 0.03]
        _pv1 = B.pv _ts1 _pv_day _f_day 103
        _diff1 = _pv1 - 100.0
    in
      testCase "PV test with curve change in middle" $
      assertEqual "simple PV with latest rate point" 100.0 _pv1
   ,
    let
      pr = B.priceBond (L.toDate "20210501")
                       (L.PricingCurve [L.TsPoint (L.toDate "20210101") 0.01, L.TsPoint (L.toDate "20230101") 0.02])
                       b1
    in
      testCase "flat rate discount " $
      assertEqual "Test Pricing on case 01" 
        (PriceResult 1978.47 65.949000 1.18 1.1881448 0.4906438 52.60 (DL.toList b1Txn)) 
        pr
    ,
     let
       b2Txn =  DL.fromList [BondTxn (L.toDate "20220301") 3000 10 300 0.08 310 0 0 Nothing S.Empty
                           ,BondTxn (L.toDate "20220501") 2700 10 500 0.08 510 0 0 Nothing S.Empty
                           ,BondTxn (L.toDate "20220701") 0 10 3200 0.08 3300 0 0 Nothing S.Empty]
       b2 = b1 { B.bndStmt = Just (S.Statement b2Txn)}

       pr = B.priceBond (L.toDate "20220201")
                        (L.PricingCurve
                            [L.TsPoint (L.toDate "20220101") 0.01
                            ,L.TsPoint (L.toDate "20220401") 0.03
                            ,L.TsPoint (L.toDate "20220601") 0.05])
                        b2
     in
       testCase " discount curve with two rate points " $
       assertEqual "Test Pricing on case 01" 
            (PriceResult 4049.10 134.97 0.44 0.364564 0.006030 286.42 (DL.toList b2Txn)) 
            pr  --TODO need to confirm in UI
    ,
    let
      b4 = b1
      pday = L.toDate "20220801"
    in
      testCase "pay prin to a bond" $
      assertEqual "pay down prin" 2400  $ B.bndBalance (B.payPrin pday 600 b4)
    ,
    let
      b5 = b1
      pday = L.toDate "20220801"
    in
      testCase "pay int to 2 bonds" $
      assertEqual "pay int" 2400  $ B.bndBalance (B.payPrin pday 600 b5)
    ,
    let 
      newCfStmt = Just $ S.Statement (DL.fromList [ BondTxn (L.toDate "20220501") 1500 300 2800 0.08 3100 0 0 Nothing S.Empty]) 
      b6 = b1 {B.bndStmt = newCfStmt}
      pday = L.toDate "20220301" -- `debug` ("stmt>>>>>"++ show (B.bndStmt b6))
      rateCurve = IRateCurve [TsPoint (L.toDate "20220201") 0.03 ,TsPoint (L.toDate "20220401") 0.04]
      --rateCurve = IRateCurve [TsPoint (L.toDate "20220201") 0.03::IRate]
    in 
      testCase "Z spread test" $
      assertEqual "Z spread test 01" 
      (Right 0.176754)
      (B.calcZspread  (100.0,pday) b6 rateCurve)
      --(B.calcZspread  (500.0,pday) (103.0,1/100) Nothing rateCurve)

  ]

bndTests = testGroup "Float Bond Tests" [
    let
       r1 = B.isAdjustble  (B.bndInterestInfo bfloat)
       r2 = B.isAdjustble (B.bndInterestInfo bfloat)
    in
      testCase "Adjust rate by Month of Year " $
      assertEqual "" [True,False] [r1,r2]
    ,
    let 
       bfloatResetInterval = bfloat {B.bndInterestInfo = B.Floater 
                                                         0.01
                                                         LPR5Y 
                                                         0.015 
                                                         QuarterEnd
                                                         DC_ACT_365F   
                                                         Nothing Nothing}
       r1 = B.isAdjustble $ B.bndInterestInfo bfloatResetInterval
       r2 = B.isAdjustble $ B.bndInterestInfo bfloatResetInterval
    in 
      testCase "Adjust rate by quarter  " $
      assertEqual "" [True,False] [r1,r2]
 ]


bndConsolTest = testGroup "Bond consoliation & patchtesting" [
    let 
      b1f = S.getTxns . B.bndStmt $ B.patchBondFactor b1
    in 
      testCase "test on patching bond factor" $
      assertEqual ""
      (DL.fromList [ BondTxn (L.toDate "20220501") 1500 10 500 0.08 510 0 0 (Just 0.5) S.Empty
       ,BondTxn (L.toDate "20220801") 0 10 1500 0.08 1510 0 0 (Just 0.0) S.Empty
      ])
      b1f,
    let 
      txns = DL.fromList [BondTxn (L.toDate "20220501") 1500 0 (-500) 0.08 0 0 0 (Just 0.5) S.Empty
              ,BondTxn (L.toDate "20220501") 2000 0 (-500) 0.08 0 0 0 (Just 0.0) S.Empty]
      bTest = b1 {B.bndStmt = Just (S.Statement txns)}
      bTestConsol = B.bndStmt $ B.consolStmt bTest
    in
      testCase "merge txn with two drawdowns" $
      assertEqual ""
      (Just (S.Statement (DL.fromList [ BondTxn (L.toDate "20220501") 2000 0 (-1000) 0.08 0 0 0 (Just 0.0) (S.TxnComments [S.Empty, S.Empty])])))
      bTestConsol,
    let 
      txns = DL.fromList [ BondTxn (L.toDate "20220501") 1500 0 (-500) 0.08 0 0 0 (Just 0.5) S.Empty
              ,BondTxn (L.toDate "20220501") 1500 0 500 0.08 0 0 0 (Just 0.0) S.Empty]
      bTest = b1 {B.bndStmt = Just (S.Statement txns)}
      bTestConsol = B.bndStmt $ B.consolStmt bTest
    in
      testCase "merge txn with one drawdown at begin" $
      assertEqual ""
      (Just (S.Statement (DL.fromList [ BondTxn (L.toDate "20220501") 1500 0 0 0.08 0 0 0 (Just 0.0) (S.TxnComments [S.Empty, S.Empty])])))
      bTestConsol,
    let 
      txns = DL.fromList [BondTxn (L.toDate "20220501") 1500 0 500 0.08 0 0 0 (Just 0.0) S.Empty,
              BondTxn (L.toDate "20220501") 2000 0 (-500) 0.08 0 0 0 (Just 0.5) S.Empty]
      bTest = b1 {B.bndStmt = Just (S.Statement txns)}
      bTestConsol = B.bndStmt $ B.consolStmt bTest
    in
      testCase "merge txn with one drawdown at end" $
      assertEqual ""
      (Just (S.Statement (DL.fromList [ BondTxn (L.toDate "20220501") 2000 0 0 0.08 0 0 0 (Just 0.5) (S.TxnComments [S.Empty, S.Empty])])))
      bTestConsol,
    let 
      txns = DL.fromList [BondTxn (L.toDate "20220501") 1500 0 500 0.08 0 0 0 (Just 0.0) S.Empty,
              BondTxn (L.toDate "20220501") 1000 0 500 0.08 0 0 0 (Just 0.5) S.Empty]
      bTest = b1 {B.bndStmt = Just (S.Statement txns)}
      bTestConsol = B.bndStmt $ B.consolStmt bTest
    in
      testCase "merge txn with one drawdown at end" $
      assertEqual ""
      (Just (S.Statement (DL.fromList [ BondTxn (L.toDate "20220501") 1000 0 1000 0.08 0 0 0 (Just 0.5) (S.TxnComments [S.Empty, S.Empty])])))
      bTestConsol
    ]


writeOffTest = 
  let 
    d1 = L.toDate "20200101"
    bnd1 = B.Bond "A" B.Sequential (B.OriginalInfo 100 d1 0.06 Nothing) (B.Fix 0.05 DC_ACT_365F) Nothing 100 0.08 0 0 0 Nothing Nothing Nothing Nothing
    writeAmt1 = 70 
    writeAmt2 = 120 
  in 
  testGroup "write off on bond" [
    testCase "write off on bond 1" $
    assertEqual "only 1st bond is written off by 70"
    (Right (bnd1 {B.bndBalance = 30,B.bndStmt = Just (S.Statement (DL.fromList [S.BondTxn d1 30.00 0.00 0.00 0.000000 0.00 0.00 0.00 Nothing (S.WriteOff "A" 70.00)]))}))
    (B.writeOff d1 writeAmt1 bnd1),
    testCase "over write off on bond 1" $
    assertEqual "over write off on bond 1"
    (Left "Insufficient balance to write off 120.00\" bond name \"\"A\"")
    (B.writeOff d1 writeAmt2 bnd1)
  ]