CLO IC Test (WIP)
import pandas as pd
pd.set_option('display.max_rows', None)
from absbox import Generic
bonds = (
("A1",{"balance":1000
,"rate":0.07
,"originBalance":1000
,"originRate":0.07
,"startDate":"2020-01-03"
,"rateType":{"Fixed":0.08}
,"bondType":{"Sequential":None}})
,("B",{"balance":1000
,"rate":0.0
,"originBalance":1000
,"originRate":0.07
,"startDate":"2020-01-03"
,"rateType":{"Fixed":0.00}
,"bondType":{"Sequential":None}
})
,("E",{"balance":500
,"rate":0.0
,"originBalance":500
,"originRate":0.07
,"startDate":"2020-01-03"
,"rateType":{"Fixed":0.00}
,"bondType":{"Equity":None}
})
)
bond_OCs = [("A1",("A1",),1.1),("B",("A1","B"),1.05)]
OC_triggers = {f"OC_{tName}":
{"condition":(("/",("poolBalance",)
,("bondBalance",*b))
,"<"
,threshold)
,"effects":None
,"status":False
,"curable":True}
for (tName,b,threshold) in bond_OCs}
bond_ICs = [("A",("A1",),1.05),("B",("A1","B"),1.03)]
IC_triggers = {f"IC_{tName}":
{"condition":["all"
,[("bondDueInt",*b),">",0]
,[("/", ("accountBalance","intAcc"),("bondDueInt",*b))
,"<"
,threshold]
]
,"effects":None
,"status":False
,"curable":True}
for (tName,b,threshold) in bond_OCs}
IC_triggerNames = list(IC_triggers.keys())
OC_triggerNames = list(OC_triggers.keys())
CLO_sample = Generic(
"CLO_sample"
,{"cutoff":"2021-03-01","closing":"2021-06-15","firstPay":"2021-07-26"
,"payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"}
,{'assets':[["Mortgage"
,{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30
,"freq":"Monthly","type":"Level","originDate":"2021-02-01"}
,{"currentBalance":2200
,"currentRate":0.02
,"remainTerm":30
,"status":"current"}]]}
,(("intAcc",{"balance":0}),("prinAcc",{"balance":0}))
,bonds
,(("seniorFee",{"type":{"annualPctFee":[("poolBalance",),0.003]},"feeStart":"2021-06-15"})
,("subFee",{"type":{"annualPctFee":[("poolBalance",),0.002]},"feeStart":"2021-06-15"})
,)
,{"amortizing":[
# intereset waterfall
# accrue and pay senior fee
["calcAndPayFee","intAcc",['seniorFee']]
# accrue interest for both bonds (NO payment)
,["calcInt","A1","B"]
# calculate OC and IC test
,['runTriggers',*(IC_triggerNames)]
# payout interest of A1
,["payInt","intAcc",["A1"]]
# if all IC test is passing
,["IfElse",[("trigger","InDistribution", "IC_A1"),False],
# if passing
[["payInt","intAcc",["B"]]
,["calcAndPayFee","intAcc",['subFee']]
,["payIntResidual","intAcc","E"]]
# if failing, redemption senior to satisfy IC
,[["payPrin","intAcc",["A1"]
,{"limit":{"formula":("bondBalance","A1")}}
]
# update IC test again
#,['runTriggers',*IC_triggerNames]
# if IC test is passing
,["If",[("trigger","InDistribution", "IC_A1"), False],
# pay interest of B and residual to E
["payInt","intAcc",["B"]],
["calcAndPayFee","intAcc",['subFee']],
["payIntResidual","intAcc","E"]]
]]
# principal waterfall
,["payPrinBySeq","prinAcc",["A1","B"]]
,["payPrinResidual","prinAcc",["E"]]
]}
,[["CollectedInterest","intAcc"]
,["CollectedPrincipal","prinAcc"]
,["CollectedPrepayment","prinAcc"]
,["CollectedRecoveries","prinAcc"]]
,None
,None
,None
,{"InDistribution":OC_triggers | IC_triggers}
,("PreClosing","Amortizing")
)
from absbox import API,mkDeal,EnginePath
localAPI = API(EnginePath.DEV,check=False)
r = localAPI.run(CLO_sample
,poolAssump = None
,runAssump = None
,read=True)
Connecting engine server -> https://absbox.org/api/dev
/home/docs/checkouts/readthedocs.org/user_builds/absbox-doc/envs/stable/lib/python3.11/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'absbox.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
✅Connected, local lib:0.52.3, server:0.52.3
/home/docs/checkouts/readthedocs.org/user_builds/absbox-doc/envs/stable/lib/python3.11/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'absbox.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
Warning Message from server:Bond E is not paid off Fee subFee is not paid off Account intAcc has cash to be distributed
r['triggers']['InWF']['IC_A1'].head(20)
| status | memo | |
|---|---|---|
| date | ||
| 2021-07-26 | False | <Tag:All:[Right 7.86 > 0.00|Right 1.7417302798... |
| 2021-08-20 | True | <Tag:All:[Right 3.39 > 0.00|Right 0.9174041297... |
| 2021-09-20 | True | <Tag:All:[Right 4.05 > 0.00|Right 0.7160493827... |
| 2021-10-20 | True | <Tag:All:[Right 4.38 > 0.00|Right 0.6392694063... |
| 2021-11-20 | True | <Tag:All:[Right 4.49 > 0.00|Right 0.5946547884... |
| 2021-12-20 | True | <Tag:All:[Right 4.22 > 0.00|Right 0.6090047393... |
| 2022-01-20 | True | <Tag:All:[Right 3.71 > 0.00|Right 0.6576819407... |
| 2022-02-20 | True | <Tag:All:[Right 2.89 > 0.00|Right 0.8062283737... |
| 2022-03-20 | False | <Tag:All:[Right 1.63 > 0.00|Right 1.3803680981... |
| 2022-04-20 | False | <Tag:All:[Right 0.75 > 0.00|Right 2.8 < 1.10]> |
| 2022-05-20 | False | <Tag:All:[Right 0.3 > 0.00|Right 6.63333333333... |
| 2022-06-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2022-06-... |
| 2022-07-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2022-07-... |
| 2022-08-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2022-08-... |
| 2022-09-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2022-09-... |
| 2022-10-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2022-10-... |
| 2022-11-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2022-11-... |
| 2022-12-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2022-12-... |
| 2023-01-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2023-01-... |
| 2023-02-20 | False | <Tag:All:[Right 0.0 > 0.00|Left "Date:2023-02-... |
r['triggers']['InWF']['IC_A1'].head(20).loc['2021-07-26'].memo
'<Tag:All:[Right 7.86 > 0.00|Right 1.7417302798982188 < 1.10]>'
r['accounts']['intAcc'].loc["2021-07-26"]
| balance | change | memo | |
|---|---|---|---|
| date | |||
| 2021-07-26 | 13.69 | -0.23 | <SeqPayFee:seniorFee> |
| 2021-07-26 | 5.83 | -7.86 | <PayInt:A1> |
| 2021-07-26 | 0.00 | -5.83 | <PayPrin:A1> |
r['accounts']['intAcc'].loc["2021-08-20"]
| balance | change | memo | |
|---|---|---|---|
| date | |||
| 2021-08-20 | 3.11 | -0.07 | <SeqPayFee:seniorFee> |
| 2021-08-20 | 0.00 | -3.11 | <PayInt:A1> |
r['bonds']['A1']
| balance | interest | principal | rate | cash | intDue | intOverInt | factor | memo | |
|---|---|---|---|---|---|---|---|---|---|
| date | |||||||||
| 2021-07-26 | 707.13 | 7.86 | 292.87 | 0.07 | 300.73 | 0.00 | 0 | 0.70713 | [[<PayInt:A1>, <PayPrin:A1>], <PayPrin:A1>] |
| 2021-08-20 | 635.07 | 3.11 | 72.06 | 0.07 | 75.17 | 0.28 | 0 | 0.63507 | [<PayInt:A1>, <PayPrin:A1>] |
| 2021-09-20 | 562.89 | 2.90 | 72.18 | 0.07 | 75.08 | 1.15 | 0 | 0.56289 | [<PayInt:A1>, <PayPrin:A1>] |
| 2021-10-20 | 490.59 | 2.80 | 72.30 | 0.07 | 75.10 | 1.58 | 0 | 0.49059 | [<PayInt:A1>, <PayPrin:A1>] |
| 2021-11-20 | 418.17 | 2.67 | 72.42 | 0.07 | 75.09 | 1.82 | 0 | 0.41817 | [<PayInt:A1>, <PayPrin:A1>] |
| 2021-12-20 | 345.63 | 2.57 | 72.54 | 0.07 | 75.11 | 1.66 | 0 | 0.34563 | [[<PayInt:A1>, <PayInt:A1>], <PayPrin:A1>] |
| 2022-01-20 | 272.97 | 2.44 | 72.66 | 0.07 | 75.10 | 1.27 | 0 | 0.27297 | [<PayInt:A1>, <PayPrin:A1>] |
| 2022-02-20 | 200.19 | 2.33 | 72.78 | 0.07 | 75.11 | 0.56 | 0 | 0.20019 | [<PayInt:A1>, <PayPrin:A1>] |
| 2022-03-20 | 126.67 | 1.63 | 73.52 | 0.07 | 75.15 | 0.00 | 0 | 0.12667 | [[<PayInt:A1>, <PayPrin:A1>], <PayPrin:A1>] |
| 2022-04-20 | 52.31 | 0.75 | 74.36 | 0.07 | 75.11 | 0.00 | 0 | 0.05231 | [[<PayInt:A1>, <PayPrin:A1>], <PayPrin:A1>] |
| 2022-05-20 | 0.00 | 0.30 | 52.31 | 0.07 | 52.61 | 0.00 | 0 | 0.00000 | [[<PayInt:A1>, <PayPrin:A1>], <PayPrin:A1>] |