π Now we have a map-based syntax suguar mkDeal to create a deal without remembering the order of arguments passed into Generic class !
name="TEST01"dates={"cutoff":"2021-03-01","closing":"2021-06-15","firstPay":"2021-07-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"}pool={'assets':[["Mortgage",{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2200,"currentRate":0.08,"remainTerm":20,"status":"current"}]]}accounts={"acc01":{"balance":0}}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":{"Equity":None}}}waterfall={"amortizing":[["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]}collects=[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]]deal_data={"name":name,"dates":dates,"pool":pool,"accounts":accounts,"bonds":bonds,"waterfall":waterfall,"collect":collects,"status":"Revolving"}fromabsboximportmkDeald=mkDeal(deal_data)## now a generic class created
Generic is a class that represent SPV which contains the dates/liabilities/assets/waterfall/trigger/hedge information.
fromabsboximportGeneric
There are 5 reusable building blocks: <DatePattern>, <Formula>, <Condition>, <Curve>, <PricingMethod>, all of them are being used in different components.
Note
It looks boring at first place to learn the BuildingBlocks but these are well designed and are essential to tackle the complexity of structured finance.
["After","YYYY-MM-DD",<datepattern>] -> a <datapattern> after βYYYY-MM-DDβ(exclusive)
["AllDatePattern",<datepattern1>,<datepattern2>.....] -> a union set of date pattern during the projection, like sum of dates
["ExcludeDatePattern",<datepattern1>,<datepattern2>.....] -> build dates from 1st <datepattern1> and exclude dates from <datepattern2>,<datepattern3>β¦
["OffsetDateDattern",<datepattern>,N] -> build dates from <datepattern> and offset days by N ( positive N move dates to future) , negative N will move dates to past )
New in version 0.30.8.
[">","YYYY-MM-DD",<datepattern>] -> a <datapattern> after βYYYY-MM-DDβ(exclusive)
[">=","YYYY-MM-DD",<datepattern>] -> a <datapattern> after βYYYY-MM-DDβ(inclusive)
["<","YYYY-MM-DD",<datepattern>] -> a <datapattern> before βYYYY-MM-DDβ(exclusive)
["<=","YYYY-MM-DD",<datepattern>] -> a <datapattern> before βYYYY-MM-DDβ(inclusive)
["+",<datepattern1>,<datepattern2>.....] -> a union set of date pattern during the projection, like sum of dates
["-",<datepattern1>,<datepattern2>.....] -> build dates from 1st <datepattern1> and exclude dates from <datepattern2>,<datepattern3>β¦
("bondBalance","A","B"...) -> sum of balance of bond A and bond B
("originalBondBalance",) -> bond balance at issuance
("originalBondBalance","A","B") -> bond balance at issuance of βAβ and βBβ
("bondDueInt","A","B") -> bond due interest for bond A and bond B
("bondDueIntOverInt","A","B") -> bond due interest over interest for bond A and bond B
("bondDueIntTotal","A","B") -> sum of interest due and interest over interest for bond A and bond B
("lastBondIntPaid","A") -> sum amount of last paid interest for bonds
("lastBondPrinPaid","A") -> sum amount of last paid principal for bonds
("behindTargetBalance","A") -> difference of target balance with current balance for the bond A
("bondTxnAmt",None,"A") -> Total transaction amount of bond βAβ
("bondTxnAmt","<PayInt:A>","A") -> Total transaction amount of interest payment bond βAβ
New in version 0.40.9.
("totalFunded","A","B") -> sum of funded amount for bond A and bond B
New in version 0.41.1.
("amountForTargetIrr",0.11,"A"), -> amount needed to make bond A reach targeted IRR rate 11%.(Make sure there is negative cash statment in the bond βAβ)
("totalFunded","A1","E") -> sum of funded amount for bond A1 and bond E
New in version 0.43.1.
("bondTargetBalance","A","B") -> sum of target balance of the bonds
New in version 0.52.3.
("irrOfBond","A") -> internal rate of return of bond βAβ.(Make sure there is negative cash statment in the bond βAβ), IRR Example (continue)
("currentPoolDefaultedBalance",) -> pool defaulted balance at last collection period
("cumPoolDefaultedBalance",) -> pool cumulative defaulted balance
("cumPoolNetLoss",) -> pool cumulative pool net loss balance
("cumPoolRecoveries",) -> pool cumulative recoveries
("cumPoolCollection",None,<field1>,<field2>....) -> pool cumulative on <fieldβ¦> fields Pool Sources
("cumPoolCollectionTill",None,N,<field1>,<field2>....) -> pool cumulative on <fieldβ¦> fields till Period N Pool Sources
("curPoolCollection",None,<field1>,<field2>...) -> pool current sum of fields
New in version 0.29.6.
("curPoolCollectionStats",None,N,<field1>,<field2>...) -> pool current sum of fields till Period N
New in version 0.24.1.
("schedulePoolValuation",<pricingmethod>,<poolname1>,<poolname2>..) -> get valuation on schedule cashflow from specific pool or all pools with Pricing Method
New in version 0.52.3.
(poolAccruedInterest,) -> get accrued interest of the pool
["Current|Defaulted",a,b] -> Applies a as factor to current balance of a performing asset; b as factor to current balance of a defaulted asset
["Current|Delinquent|Defaulted",a,b,c] -> same as above ,but with a b applies to an asset in deliquency.
["PV|Defaulted",a,b] -> using a as pricing curve to discount future cashflow of performing asset while use b as factor to current balance of defautled asset.
New in version 0.52.3.
{"currentFactor":a,"defaultFactor":b} -> same as above but with more key strike
if deal is ongoing ( which has been issued ), the difference is that in PreClosing mode, the projection will include an event of OnClosingDate which describe a sequence of actions to be performed at the date of closing
a DatePattern, describe the dates that collect cashflow from pool
payFeq
a DatePattern, describe the dates that distribution funds to fees and bonds.
{"collect":["2022-11-01"# last pool collection date,,"2022-12-01"]# next pool collection date,"pay":["2022-11-15"# last distribution payment date,,"2022-12-15"]# next distribution date,"stated":"2030-01-01","poolFreq":"MonthEnd","payFreq":["DayOfMonth",20]}
Usually pool collection date is prior to waterfall payment date in a single cycle.
Pool cash proceeds were deposit to accounts at poolFreq date.
Engine will pick a waterfall base on dealstatus to exectue waterfall on payFreq date
payFreq and poolFreq can be same day,and engine will run pool collection before waterfall execution by default.
Examples:
# quaterly pay dates["EveryNMonth","2019-9-15",3]# 2019-12-15,2020-03-15...# monthly pay["DayOfMonth",10],# every 10th of month after first pay date or next pay date
similar to percentage fee but it will use an annualized rate to multiply the value of Formula.
either reference to pool balance or bond balance , etcβ¦. it will accure type fee, which if not being paid, it will increase the due amount.
# each period new fee due 100{"flowByBondPeriod":[[1,100],[2,200],[3,300]]}# each period new fee due 100,50,25{"flowByPoolPeriod":[[1,100],[2,150],[3,175]]}
("VAT",{"type":{"targetBalanceFee":[("poolBalance",),("bondBalance",)]},"feeStart":"2022-01-01"})# the fee is total funded amount * 1%("upfrontFee",{"type":{"targetBalanceFee":[("*",("totalFunded","A1","E"),0.01),("feeTxnAmt",None,"upfrontFee")]}})
Make sure you will change the collectionrule as well to instruct the deal pick different fields from multiple pools.
See exmaple: Multiple Pool with mixed assets
Buy-To-Let type usually refers to a mortgage which only requires interest payment and a bullet principal at last period.
User just need to set I_P in field type, which means Interest comes first then Principal comes last.
The retnal can be increasing/decreasing by a fixed rate or a vector of rate.
User can supply with an extra params stepUp to indicate the contractual rental change:
flatRate -> the rental will increase by a fixed rate after each period
byRates -> the rental will increase by a vector of rates, which means the rental will change by each period according to the vector
flatAmount -> the rental will increase by a fixed amount after each period
byAmounts -> the rental will increase by a vector of amounts, which means the rental will change by each period according to the vector
FixedAsset is a generalized concept to model couple different types of fixed asset which generates cashflow :
capacity : max number of unit generated by the asset in a given period
* capacity multiply utility rate from assumption ,then will yield ACTUAL number of marketable unit
uniteprice : input from assumption, which used to set market price of unit to be sold
This type of asset can be used to model future flow of securitization : Hotel room / Solar Panel / EV Charger Station or anything which has a cap on capacity and it would generate something to sell.
capacity
can be a fixed value or a curve which depends on remainTerm, which suggests the production would decrease with time, for example, the Solar Panel . or a fixed value, like a Hotel Room
amortize
either Straight or DecliningBalance .
Warning
Changed in version 0.45.3: βBalanceβ becomes a required field in status of FixedAsset type asset.
Receivable is a type of asset which has a fixed amount of receivable in last period, with optional fee collected at end. It may represent Invoice , Account Receivable , Trade Receivable in the real world.
if No fee was set, the receivable will be paid off at last period without fee.
if there are multiple fee being setup, then ALL fees are sum up and paid off at last period.
syntax
["Invoice",{<assetdescription>},{<status>}]
assetdescription -> a map describe the asset
status -> a map describe the status of asset
feeType
Fixed -> fixed amount of fee
FixedRate -> fixed rate of fee, the base was the originBalance
AdvanceRate -> annualized rate of advance amount, the base was the originAdvance
Itβs possible that to build a asset model with ProjectedCashflow which means the there is no way to get loan level data of Assets but only projected cashflow derived from these assets.
[["CollectedInterest",[[0.8,"acc01"],[0.2,"acc02"]]]# 80% of interest will be allocated to acc01# 20% of interest will be allocated to acc02,["CollectedPrincipal",[[0.8,"acc01"],[0.2,"acc02"]]],["CollectedPrepayment","acc01"]# 100% of prepayment will be allocated to acc01,["CollectedRecoveries","acc01"]]# multiple pools[[["PoolA",],"CollectedInterest","acc01"],[["PoolB",],"CollectedInterest","acc03"],[["PoolA","PoolB"],"CollectedPrincipal",[0.7,"acc02"],[0.3,"acc03"]]]# using dict syntax[{"source":"CollectedInterest","accountByPct":{"acc01":0.8,"acc02":0.2}},{"poolId":['PoolA','PoolB'],"source":"CollectedPrincipal","accountByPct":{"acc01":0.8,"acc02":0.2}},{"source":"CollectedPrepayment","account":"acc01"},{"poolId":['PoolA','PoolB'],"source":"CollectedRecoveries","account":"acc01"}]
Fix Amount: a single reserve amount which fixed during cashflow projection
syntax
("fix",<Amount>)
("fixReserve",<Amount>)
# 3 forms are all valid and same("ReserveAccountA",{"balance":0,"type":{"fixReserve":1000}})("ReserveAcc1",{"balance":100,"type":("fix",1000)})("ReserveAcc2",{"balance":100,"type":("fixReserve",1000)})
Formula: the target reserve amount is derived from a Formula , like 2% of pool balance
In the example above, it says, the bond will accrue interest by 100% * Pool Weighted Average Coupon. The rate will be reset by every end of the month.(Which described by a DatePattern)
Simplify the reference to multiple bonds. For example, there are couple senior bonds A-1 A-2 A-3, user can reference them as a group A instead of A-1 A-2 A-3
It offers short cut to paydown by different way: by interst rate, by maturity date, by pro-rata, by bond names.
In the future, it may offer a way to issue new bonds.( New bond will be inserted into that group)
Multi Interest Bond is a bond with multiple interest rates. It is being used to model bond with different priority of interest components for single bond, like βsub ordinated interest/senior interestβ
("A1",{"balance":1200,"rates":[0.05,0.02],"originBalance":1200,"originRate":0.07,"startDate":"2020-01-03","rateTypes":[("fix",0.05),("fix",0.02)],"bondType":{"Sequential":None}})# to accrual interest,["calcInt","A1"]# to pay out first interest component,["payIntByIndex","acc01",["A1"],0],["payIntByIndex","acc01",["A1"],1]
Waterfall means a list of Action to be executed. A Deal may have more than one waterfalls.
It was modeled as a map, with key as identifier to distinguish different type of waterfall.
Waterfall which being executed multiple times during deal run:
Watefall exectued on deal distribution day:
When deal status is Amortizing:
"amortizing" -> will be picked when deal status is Amortizing
When deal status is Accelerated:
("amortizing","accelerated") -> will be picked when deal status is Accelerated
When deal status is Defaulted:
("amortizing","defaulted") -> will be picked when deal status is Defaulted
When deal status is Revolving:
"revolving" -> will be picked when deal status is Revolving
If there is no waterfall matched, the default waterfall will be picked:
"default" -> the default waterfall to be executed if no other waterfall applicable
Watefall exectued on pool collection day:
"endOfCollection" -> will be exectued at the end of each collection period
Waterfall which being executed only once :
"cleanUp" ->
will be exectued once when deal is being clean up call
will be exectued once when deal running till end
"closingDay" -> will be exectued once at the Day of Closing if deal status is PreClosing ( only valid for deals in PreClosing status)
Waterfall exectued on custom dates:
New in version 0.41.3.
(custom,<waterfallname>) -> will be exectued on custom date, with waterfall name as identifier. Make sure to define DatePattern in deal dates Deal Dates .
Make sure there are waterfall to be run if deal status changed. Otherwise the deal wonβt do anything. ie. if there is trigger change in deal,make sure there is a corresponding waterfall modeled for that status.
Action
Action is a python list, whose elements annoates the parameter of action. In most of cases,
the first element of list is the name of action,
rest of elements are describing the fund movements(fund source and fund target)/ state change like update trigger status / fee accrual /bond interest accrual.
sell the assets and deposit the proceeds to the account
syntax
["sellAsset",{pricingmethod},{Account}] , liquidate all pools
New in version 0.29.9.
["sellAsset",{pricingmethod},{Account},[{PoolId}]] , liquidate pools in the list
Pricing Method:
"PvRate" : sell the assets with price of PV of asset or defaulted price
"Current|Defaulted" : sell the assets with price of Current price or defaulted price
["sellAsset",["PvRate",0.05],"acc01"]# PV of future cashflow with 5% discount rate["sellAsset",["Current|Defaulted",0.9,0.2],"acc01"]# 90% of current performing balance and 20% un-recovered defaulted balance["sellAsset",{"currentFactor":0.9,"defaultFactor":0.2},"acc01"]["sellAsset",{"PvRate":0.05},"acc01"]
LiquidityFacility behaves like a 3rd party entity which support the cashflow distirbution in case of shortage. It can deposit cash to account via liqSupport, with optinal a <limit>.
As return, LiquidityFacility will receive :
* interest payment from the line of credit drawed
* fee payment from the line of credit un-drawed
this action will query the Formula and tag with <Comment>(βoptional stringβ) save to result.
To read the result, please refer to View Variables In Waterfall
Trigger is a generalized concept in absbox / Hastructure, it is not limited to pool performance which is design to protect tranches but a border concept as below:
There are 4 fields in Triggers:
Condition -> it will fire the trigger effects, when Condition is met
Effects -> what would happen if the trigger is fired
Status -> it is triggered or not (if missing, default is False)
Curable -> whether the trigger is curable (if missing, default is False)
Start/End of each Pool Collection Day -> BeforeCollect / AfterCollect
Start/End of each Distribution Day -> BeforeDistribution / AfterDistribution
During any point of waterfall
Why there are 5 points of time to run trigger ?
Because , when there is a Formula ,it may reference to bond balance ,but the bond balance varies in different point of time : the balance in BeforeDistribution is different from AfterDistirbution as the bond maybe paid down.
"effects":("newStatus","Amortizing")# change deal status to "Amortizing""effects":("newStatus","Accelerated")# change deal status to "Accelerated""effects":("newStatus","Defaulted")# change deal status to "Defautled"
Once the state of deal changed, the deal will pick the corresponding waterfall to run at distribution days.
<Rate Type> : type of rate, Interest from bond interest rate type section
<init rate> : initial rate to be set
Combination of above
a list of above can be combined together with keyword Effects
"effects":["Effects",<effect1>,<effect2>,....]
Examples
{"BeforeCollect":{},"AfterCollect":{"Mytrigger0":{"condition":[("cumPoolDefaultedRate",),">",0.05],"effects":("newStatus","Defaulted"),"status":False,"curable":False}},"BeforeDistribution":{"Mytrigger1":{"condition":[">=","2025-01-01"],"effects":("newStatus","Defaulted"),"status":False,"curable":False}},"AfterDistribution":{"Mytrigger2":{"condition":[("bondFactor",),"<=",0.1],"effects":("newStatus","Accelerated"),"status":False,"curable":False}}}#a list of triggers effects{"AfterCollect":{"Mytrigger3":{"condition":[("cumPoolDefaultedRate",),">",0.05],"effects":("Effects",("newStatus","Defaulted"),("accrueFees","feeA","feeB")),"status":False,"curable":False}}}# ALL and ANY logic of triggers ( and they can nested toghter ! ),{"AfterCollect":{"Mytrigger4":{"condition":["any",[("cumPoolDefaultedRate",),">",0.05],[">","2021-09-15"]],"effects":("newStatus","Accelerated"),"status":False,"curable":False}}},{"AfterCollect":{"Mytrigger5":{"condition":["all",[("cumPoolDefaultedRate",),">",0.05],[">","2021-09-15"]],"effects":("newStatus","Accelerated"),"status":False,"curable":False}}}
Liquidity Provider is an external entity which can be used as a line of credit/insuer.
If there is a shortage on fee or interest or principal, user can setup rules to draw cash from the Liquidity Provider and deposity cash to accounts.
The optional attributes of fee and rate can be used to model service fee or interest to be charged.
common properites
start -> when the liquidity provider start to be effective
credit -> current available credit to draw, served as base to calculate premium fee
balance -> current balance to be paid back to provider, served as base to calculate interest
Interest Rate Swap is a 3rd party entity ,which can either deposit money into a SPV or collecting money from SPV. The direction of cashflow depends on the strike rate vs interest rate curve in assumption.
it was modeled as a map ,with key as name to swap ,value serve as properties to swap. The very reason using a map becasue a deal can have multiple Swap contract.
Fields:
settleDates -> DatePattern,describe the settlement dates .
pair -> describe rates to swap (paying rate in left, receiving rate on right)
it can be float to float , fix to float , or float to fix
base -> Index Hedge Base,describe how reference balance is being updated
start -> when the swap contract come into effective
balance -> (optional), current reference balance
lastSettleDate -> (optional), last settle date which calculate netcash
netcash -> (optional), current cash to pay/to collect
stmt -> (optional),transaction history
example:
swap={"swap1":{"settleDates":"MonthEnd","pair":[("LPR5Y",0.01),0.05]# paying a float rate with spread ,and receiving a fix annualized rate,"base":{"formula":("poolBalance",)},"start":"2021-06-25","balance":2093.87}}
Ledger conceptually was introduced similar to the same term in accounting. It is just a book which records transaction,for each transaction :
it is either Credit or Debit
it has amount
it has a oustanding balance
It can be booked in waterfall action Booking Ledger ,and it can be query oustanding balance /transaction amount Ledger .
The Ledger wonβt hold any cash,but serve a purpose of record. The balance or transaction amount of Ledger was used to calculate amount to pay bonds or transfer amount in accounts.
Enough explain for finance people, from engineering pespective, Ledger is just a statefulvariable during whole projection period π
Schedule Cashflow with Default Amount Vector as assumptionο
New in version 0.22.
set default amount vector as assumption
use schedule input cashflow
flow=[["2022-10-28",200.0,100],["2022-11-28",200.0,100],["2022-12-28",200.0,100],["2023-01-28",200.0,100],["2023-02-28",200.0,100],["2023-03-28",200.0,100],["2023-04-28",200.0,100],["2023-05-28",200.0,100],["2023-06-28",200.0,100],["2023-07-28",200.0,100],["2023-08-28",200.0,100]]deal_data={"name":"Default Amount to Schedule Flow","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["ProjectedCashflow",2200,"2021-06-01",flow,"MonthEnd"]]},"accounts":{"acc01":{"balance":0}},"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}}},"collect":[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],"waterfall":{"Amortizing":[["payFee","acc01",['trusteeFee']],["calcInt","A1"],["payInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]},"status":("PreClosing","Amortizing")}if__name__=="__main__":fromabsboximportAPI,mkDeallocalAPI=API("http://localhost:8081",check=False)deal=mkDeal(deal_data)r=localAPI.run(deal,poolAssump=("Pool",("Mortgage",{"ByAmount":(300,[0.1,0.2,0.6,0.1])},None,{"Rate":0.7,"Lag":10},None),None,None),runAssump=None,read=True)r['pool']['flow']
User can set step-up bond either in one-off basis or increase via a DatePattern
step-bond is just an optinal field in the bond map
user can aggregate all cash in a single aggregation rule
fromabsboximportGenerictest01=Generic("Bond Step Up",{"collect":["2022-05-01","2022-06-01"],"pay":["2022-06-15","2022-07-15"]# next distribution date,"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.08,"remainTerm":20,"status":"current"}]],'issuanceStat':{"IssuanceBalance":10000}},(("acc01",{"balance":0}),),(("A1",{"balance":1000,"rate":0.07,"originBalance":1000,"originRate":0.07,"startDate":"2020-01-03","rateType":{"Fixed":0.08},"stepUp":("ladder","2022-12-01",0.01,"MonthEnd"),"bondType":{"Sequential":None}}),("B",{"balance":1000,"rate":0.0,"originBalance":1000,"originRate":0.00,"startDate":"2020-01-03","rateType":{"Fixed":0.00},"bondType":{"Equity":None}})),(("trusteeFee",{"type":{"fixFee":30}}),),{"amortizing":[["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payIntResidual","acc01","B"]]},[["CollectedCash","acc01"]],None,None,None,None,"Amortizing")
user can set action to pay bond sequentially, with option to set up a limit tie up to Formula
fromabsboximportGenerictest01=Generic("Pay Prin Seq",{"collect":["2022-05-01","2022-06-01"],"pay":["2022-06-15","2022-07-15"]# next distribution date,"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.08,"remainTerm":20,"status":"current"}]],'issuanceStat':{"IssuanceBalance":10000}},(("acc01",{"balance":0}),),(("A1",{"balance":500,"rate":0.07,"originBalance":500,"originRate":0.07,"startDate":"2020-01-03","rateType":{"Fixed":0.08},"bondType":{"Sequential":None}}),("A2",{"balance":500,"rate":0.07,"originBalance":500,"originRate":0.07,"startDate":"2020-01-03","rateType":{"Fixed":0.08},"bondType":{"Sequential":None}}),("B",{"balance":1000,"rate":0.0,"originBalance":1000,"originRate":0.00,"startDate":"2020-01-03","rateType":{"Fixed":0.00},"bondType":{"Equity":None}})),(("trusteeFee",{"type":{"fixFee":30}}),),{"amortizing":[["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1","A2"]]#,["payPrinBySeq","acc01",["A1","A2"]],["payPrinBySeq","acc01",["A1","A2"],{"limit":{"formula":("constant",100)}}],["payPrin","acc01",["B"]],["payIntResidual","acc01","B"]]},[["CollectedCash","acc01"]],None,None,None,None,"Amortizing")
fromabsboximportAPI,mkDeallocalAPI=API("http://localhost:8081",check=False)deal_data={"name":"Multiple Pools with Mixed Asset","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{"PoolA":{'assets':[["Mortgage",{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2200,"currentRate":0.08,"remainTerm":30,"status":"current"}]]},"PoolB":{'assets':[["Loan",{"originBalance":80000,"originRate":["floater",0.045,{"index":"SOFR3M","spread":0.01,"reset":"QuarterEnd"}],"originTerm":60,"freq":"Monthly","type":"i_p","originDate":"2021-02-01"},{"currentBalance":65000,"currentRate":0.06,"remainTerm":60,"status":"Current"}]]}},"accounts":{"acc01":{"balance":0},"acc02":{"balance":0}},"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}},"serviceFee":{"type":{"annualPctFee":[("poolBalance","PoolB"),0.02]}},"serviceFee2":{"type":{"byTable":["MonthEnd",("const",1),[(0,5),(2,10),(10,15)]]}}},"collect":[[["PoolA"],"CollectedCash","acc01"],[["PoolB"],"CollectedCash","acc02"]],"waterfall":{"Amortizing":[["payFee","acc01",['trusteeFee']],["calcAndPayFee","acc01",['serviceFee']],["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]},"status":("PreClosing","Amortizing")}deal=mkDeal(deal_data)r=localAPI.run(deal,poolAssump=("ByName",{"PoolA":(("Mortgage",{"CDR":0.02},None,None,None),None,None),"PoolB":(("Loan",{"CDR":0.01},None,None,None),None,None)}),runAssump=[("interest",("LIBOR6M",0.04),("SOFR3M",0.04)),("inspect",("MonthEnd",("poolBalance","PoolB")),("MonthEnd",("poolBalance","PoolA")))],read=True)#r['pool']['flow']['PoolB']
Resecuritizaiton deal is a deal which is backed by a pool of other deals. Itβs a deal of deal.
A pool is composed of a list of bonds, each of bonds are affliated to other deals.
The relationship is being defined by a tuple :
(<BondName>,<PctofBond>,<DateofAcquisition>)
In this example , test01 is the undelrying bond which is modeled at line:54
<BondName> -> which bond cashflow to be flow into the pool
<Pct of Bond> -> pct of cashflow to be flow into the pool
<Date of Acquisition> -> since when the bond cashflow to be flow into the pool
User can set assumption to undelrying deal at line:93-97
itβs a tuple to describe assumption of underlying deals: ("ByDealName",<anassumptionmap>)
Liquidity provider will deposit the gap amount of interest due against the account available balance.
And it will start to be repaid if both A1 and B tranche were paid off
Fixed amount with interest accured.
fromabsboximportGenerictest05=Generic("liquidation provider with interest",{"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":1600,"currentRate":0.08,"remainTerm":20,"status":"current"}]]},(("acc01",{"balance":0}),("acc02",{"balance":0})),(("A1",{"balance":1000,"rate":0.08,"originBalance":1000,"originRate":0.08,"startDate":"2020-01-03","rateType":{"Fixed":0.09},"bondType":{"Sequential":None}}),("B",{"balance":500,"rate":0.05,"originBalance":500,"originRate":0.05,"startDate":"2020-01-03","rateType":{"Fixed":0.10},"bondType":{"Sequential":None}})),tuple(),{"amortizing":[["calcInt","A1"],["liqAccrue","insuranceProvider"],["liqSupport","insuranceProvider","interest","A1"],["liqSupport","insuranceProvider","interest","B"],["accrueAndPayInt","acc01",["A1","B"]],["payPrin","acc02",["A1"]],["payPrin","acc02",["B"]],["If",[("bondBalance","A1","B"),"=",0],["accrueAndPayInt","acc02",["A1","B"]],["liqRepay","balance","acc01","insuranceProvider"],["liqRepay","balance","acc02","insuranceProvider"]]]},[["CollectedInterest","acc01"],["CollectedPrincipal","acc02"],["CollectedPrepayment","acc02"],["CollectedRecoveries","acc02"]],{"insuranceProvider":{"lineOfCredit":100,"start":"2021-06-15","fixRate":0.01,"rateAccDates":"MonthEnd","lastAccDate":"2021-06-15"}},None,None,None,("PreClosing","Amortizing"))
Using a formula to cap the support amount.
fromabsboximportGenerictest01=Generic("liquidation provider with interest",{"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":1200,"currentRate":0.08,"remainTerm":20,"status":"current"}]]},(("acc01",{"balance":0}),("acc02",{"balance":0})),(("A1",{"balance":1000,"rate":0.08,"originBalance":1000,"originRate":0.08,"startDate":"2020-01-03","rateType":{"Fixed":0.09},"bondType":{"Sequential":None}}),("B",{"balance":500,"rate":0.05,"originBalance":500,"originRate":0.05,"startDate":"2020-01-03","rateType":{"Fixed":0.10},"bondType":{"Sequential":None}})),tuple(),{"amortizing":[["calcInt","A1"],["liqSupport","insuranceProvider","account","acc01",{"formula":("Max",("substract",("bondDueInt","A1","B"),("accountBalance","acc01")),("constant",0.0))}],["accrueAndPayInt","acc01",["A1","B"]],["payPrin","acc02",["A1"]],["payPrin","acc02",["B"]],["If",[("bondBalance","A1","B"),"=",0],["accrueAndPayInt","acc02",["A1","B"]],["liqRepay","bal","acc01","insuranceProvider"],["liqRepay","bal","acc02","insuranceProvider"]]]},[["CollectedInterest","acc01"],["CollectedPrincipal","acc02"],["CollectedPrepayment","acc02"],["CollectedRecoveries","acc02"]],{"insuranceProvider":{"lineOfCredit":100,"start":"2021-06-15","type":{"Reset":"MonthEnd","Formula":("bondBalance",),"Pct":0.0015}}},None,None,None,("PreClosing","Amortizing"))
The deal docs may split income from pools by pct% to another account
fromabsboximportGenerictest04=Generic("split pool income",{"cutoff":"2021-03-01","closing":"2021-06-15","firstPay":"2021-07-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},{'assets':[["Mortgage",{"originBalance":2500,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2500,"currentRate":0.08,"remainTerm":20,"status":"current"}]]},(("acc01",{"balance":0}),("acc02",{"balance":0})),(("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":{"Equity":None}})),(("trusteeFee",{"type":{"fixFee":30}}),),{"amortizing":[["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payIntResidual","acc01","B"]]},[["CollectedInterest",[0.8,"acc01"],[0.2,"acc02"]],["CollectedPrincipal",[0.8,"acc01"],[0.2,"acc02"]],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],None,None,None,None,("PreClosing","Amortizing"))
The transaction may require different aggregation rule base on a certain Pre or Deal Status.
i.e if a deal failed in certain test ,then interest portion may aggregate into principal account.
We may just model dummy accounts which accepts Principal and Interest .
But in the Pool Collection waterfall, we can move the funds around base on βifβ action in the waterfall, which can be test certain test pass or fail or test current deal status.
deal_data={"name":"AggRule by condition","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["Mortgage",{"originBalance":2400,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2400,"currentRate":0.08,"remainTerm":30,"status":"current"}]]},"accounts":{"prinAcc":{"balance":0},"intAcc":{"balance":0}},"bonds":{"A1":{"balance":800,"rate":0.07,"originBalance":800,"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}}},"collect":[["CollectedInterest","intAcc"],["CollectedPrincipal","prinAcc"],["CollectedRecoveries","prinAcc"],["CollectedPrepayment","prinAcc"]],"waterfall":{"default":[["If",["status","Defaulted"],["transfer","intAcc","prinAcc"]],["payFee","intAcc",['trusteeFee']],["accrueAndPayInt","intAcc",["A1"]],["payPrin","prinAcc",["A1"]],["payPrin","prinAcc",["B"]],["payPrinResidual","prinAcc",["B"]]]},"status":("PreClosing","Amortizing")}fromabsboximportAPI,mkDeallocalAPI=API("http://localhost:8081",check=False)trigger={"AfterCollect":{"poolDef":{"condition":[("cumPoolDefaultedBalance",),">",20],"effects":("newStatus","Defaulted"),"status":False,"curable":False}}}deal=mkDeal(deal_data|{"triggers":trigger})r=localAPI.run(deal,poolAssump=("Pool",("Mortgage",{"CDR":0.02},{"CPR":0.02},None,None),None,None),runAssump=[],read=True)#r['accounts']['intAcc']
There can be multiple waterfalls which corresponding to status.
a acceleration/turbo event could be triggered and changing the payment sequence
amortizing
revolving
accelerated
defaulted
clean up
fromabsboximportGenerictest01=Generic("Multiple Waterfall",{"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.08,"remainTerm":20,"status":"current"}]]},(("acc01",{"balance":0}),),(("A1",{"balance":500,"rate":0.07,"originBalance":500,"originRate":0.07,"startDate":"2020-01-03","rateType":{"Fixed":0.08},"bondType":{"Sequential":None}}),("A2",{"balance":500,"rate":0.07,"originBalance":500,"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":{"Equity":None}})),(("trusteeFee",{"type":{"fixFee":30}}),),{"amortizing":[["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1","A2"]],["payPrin","acc01",["A1","A2"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]],"cleanUp":[],"endOfCollection":[]# execute when collect money from pool,("amortizing","defaulted"):[]#execute when deal is `defaulted`,("amortizing","accelerated"):[#execute when deal is `accelerated`["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1","A2"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["A2"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]},[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],None,None,None,{"AfterCollect":{"DefaultTrigger":{"condition":[("cumPoolDefaultedRate",),">",0.05],"effects":("newStatus","Accelerated"),"status":False,"curable":False}}},("PreClosing","Amortizing"))
deal_data={"name":"Sample with Interest Swap","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["Mortgage",{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2200,"currentRate":0.08,"remainTerm":30,"status":"current"}]]#,'issuanceStat':{"HistoryDefaults":5}},"accounts":{"acc01":{"balance":0}},"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}}},"collect":[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],"waterfall":{"Amortizing":[["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]},"status":("PreClosing","Amortizing")}fromabsboximportAPI,mkDeallocalAPI=API("http://localhost:8081",check=False)trigger={"AfterCollect":{"poolDef":{"condition":[("cumPoolDefaultedBalance",),">",20],"effects":("newStatus","Defaulted"),"status":False,"curable":False}}}deal=mkDeal(deal_data|{"triggers":trigger})r=localAPI.run(deal,poolAssump=("Pool",("Mortgage",{"CDR":0.02},None,None,None),None,None),runAssump=[("inspect",("MonthEnd",("cumPoolDefaultedBalance",))),("call",{"afterDate":"2022-09-01"}),("fireTrigger",[("2021-10-01","AfterCollect","poolDef")])],read=True)r['result']['status']r['result']['logs']
User can specify a conditional clause in the waterfall.
Only the conditions were met, actions following will be executed.
fromabsboximportGenerictest03=Generic("If in Waterfall",{"cutoff":"2021-03-01","closing":"2021-06-15","firstPay":"2021-07-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},{'assets':[["Mortgage",{"originBalance":2500,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2500,"currentRate":0.08,"remainTerm":20,"status":"current"}]]},(("acc01",{"balance":0}),),(("A1",{"balance":500,"rate":0.07,"originBalance":500,"originRate":0.07,"startDate":"2020-01-03","maturityDate":"2022-03-31","rateType":{"Fixed":0.08},"bondType":{"Sequential":None}}),("A2",{"balance":500,"rate":0.07,"originBalance":500,"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":{"Equity":None}})),(("trusteeFee",{"type":{"fixFee":30}}),),{"amortizing":[["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1","A2"]],["If",[("monthsTillMaturity","A1"),"<",3],["payPrin","acc01",["A1","A2"]]],["payPrin","acc01",["B"]],["payIntResidual","acc01","B"]]},[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],None,None,None,None,("PreClosing","Amortizing"))
query the ledger balance via formula ("ledgerBalance","<ledgerName>")
* it can be reference in all places in waterfall/trigger/accounts which are applicable to Formula
deal_data={"name":"Sample with Interest Swap","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["Mortgage",{"originBalance":2400,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2400,"currentRate":0.08,"remainTerm":30,"status":"current"}]]#,'issuanceStat':{"HistoryDefaults":5}},"accounts":{"acc01":{"balance":0}},"bonds":{"A1":{"balance":800,"rate":0.07,"originBalance":800,"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}}},"collect":[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedRecoveries","acc01"],["CollectedPrepayment",[0.5,"acc01"]]],"waterfall":{"Amortizing":[["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["bookBy",["formula","myLedger","Credit",("constant",100)]],["payPrinResidual","acc01",["B"]]]},"status":("PreClosing","Amortizing"),"ledgers":{"myLedger":{"balance":100}}}fromabsboximportAPI,mkDeallocalAPI=API("http://localhost:8081",check=False)deal=mkDeal(deal_data)r=localAPI.run(deal,poolAssump=("Pool",("Mortgage",{"CDR":0.02},{"CPR":0.02},None,None),None,None),runAssump=[("inspect",["MonthEnd",("ledgerBalance","myLedger")])],read=True)r['ledgers']['myLedger']r['result']['inspect']
the core concept of YieldSupplementOvercollateralization is
price the Schduled cashflow of the pool remaining with two rates ( rate being used, and a rate of higher of required rate or rate being used )
subtract the two value, thatβs amount can be used to paid off to ensure a senior tranche is safe comparing to balance of pool
usually , there is a constant OC% needed to inflat the OC over adjusted pool balance.
New in version 0.24.1.
fromabsboximportAPI## make sure it is greater with 0.24.1localAPI=API("http://localhost:8081",lang='english',check=False)deal_data={"name":"Yield Supplement Overcollateralization","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["Mortgage",{"originBalance":1300,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-03-05"},{"currentBalance":1300,"currentRate":0.08,"remainTerm":30,"status":"current"}]]},"accounts":{"acc01":{"balance":100},"acc02":{"balance":0}},"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":{"Equity":None}}},"fees":{},"collect":[["CollectedCash","acc01"]],"waterfall":{"Amortizing":[["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"],{"limit":{"formula":("-",("schedulePoolValuation",('PvRate',("poolWaRate",))),("schedulePoolValuation",('PvRate',("max",("poolWaRate",),("const",0.09)))))}}],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]},"status":("PreClosing","Amortizing")}if__name__=='__main__':fromabsboximportAPIlocalAPI=API("https://absbox.org/api/latest")fromabsboximportmkDealdeal=mkDeal(deal_data)r=localAPI.run(deal,poolAssump=None,runAssump=[("inspect",(["DayOfMonth",20],("schedulePoolValuation",('PvRate',0.08))),(["DayOfMonth",20],("schedulePoolValuation",('PvRate',("max",("poolWaRate",),("const",0.09))))),(["DayOfMonth",20],("-",("schedulePoolValuation",('PvRate',("poolWaRate",))),("schedulePoolValuation",('PvRate',("max",("poolWaRate",),("const",0.09)))))))],read=True)# from absbox.local.util import unifyTs# unifyTs(r['result']['inspect'].values())# r['bonds']['A1']
deal_data={"name":"Sample with Interest Cap","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["Mortgage",{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2200,"currentRate":0.08,"remainTerm":30,"status":"current"}]]},"accounts":{"acc01":{"balance":0}},"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}}},"collect":[["CollectedCash","acc01"]],"waterfall":{"Amortizing":[['settleCap',"acc01","cap1"],["payFee","acc01",['trusteeFee']],["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]},"status":("PreClosing","Amortizing")}fromabsboximportAPI,mkDeallocalAPI=API("http://localhost:8081",check=False)cap={"cap1":{"index":"LIBOR6M","strike":[("2022-01-01",0.02),("2023-01-01",0.03),("2024-01-01",0.05)],"base":{"fix":10000},"start":"2022-01-01","end":"2025-01-01","settleDates":"QuarterEnd","rate":0.035,"lastSettleDate":None,"netCash":100}}deal=mkDeal(deal_data|{"rateCap":cap})r=localAPI.run(deal,poolAssump=("Pool",("Mortgage",{"CDR":0.02},None,None,None),None,None),runAssump=[("interest",("LIBOR6M",0.04))],read=True)#r['rateCap']['cap1']
using shortcut mkDeal to create a generic deal object
Swap can reference a notion with a formula
fromabsboximportmkDeal,APIdeal_data={"name":"Sample with Interest Swap","dates":{"cutoff":"2021-03-01","closing":"2021-06-15","firstPay":"2021-07-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["Mortgage",{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2200,"currentRate":0.08,"remainTerm":20,"status":"current"}]]},"accounts":{"acc01":{"balance":0}},"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}}},"collect":[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],"waterfall":{"amortizing":[["payFee","acc01",['trusteeFee']],["settleSwap","acc01","swap1"],["accrueAndPayInt","acc01",["A1"]],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]}}if__name__=='__main__':fromabsboximportAPIlocalAPI=API("http://localhost:8081")swap={"swap1":{"settleDates":"MonthEnd","pair":[("LPR5Y",0.01),0.05],"base":{"formula":("poolBalance",)},"start":"2021-06-25","balance":2093.87}}deal=mkDeal(deal_data|{"rateSwap":swap})r=localAPI.run(deal,runAssump=[("interest",("LPR5Y",[["2022-01-01",0.05],["2023-01-01",0.06]]))],read=True)r['rateSwap']['swap1']
Revolving structure with revolving asset perf assumption / pricing method
Formula based way to transfer cash between accounts
Conditional action base on trigger
IF-ELSE clause in waterfall action
Pay residual value to a fee
Buy/Sell asset via a pricing method
fromabsboximportGenericprojectedFlow=[["2022-10-28",296926650.8,39621199.84],["2022-11-28",298390935.6,38239799.05],["2022-12-28",335984118.4,36851240.88],["2023-01-28",314109521.1,35270038.62],["2023-02-28",270390234,33782877.37],["2023-03-28",270350575.7,32518759.42],["2023-04-28",265338646.5,31253463.48],["2023-05-28",259803763.2,30007334.46],["2023-06-28",259946092.9,28787675.09],["2023-07-28",252857713.1,27564949.08],["2023-08-28",246910947.3,26372771.15],["2023-09-28",243696488.3,25204562.17],["2023-10-28",236448505.8,24048073.72],["2023-11-28",233663343.6,22921167.01],["2023-12-28",229235118.7,21803634.86],["2024-01-28",226586431.3,20702493.3],["2024-02-28",220466563.4,19610562.99],["2024-03-28",215780992.7,18542390.48],["2024-04-28",209777601.8,17492785.73],["2024-05-28",205412585.4,16466179.14],["2024-06-28",204576491.7,15458135.54],["2024-07-28",191983415.7,14454511.7],["2024-08-28",183351318.5,13500336.9],["2024-09-28",173452976.8,12579982.71],["2024-10-28",166901231.6,11699767.21],["2024-11-28",160900371.7,10848388.31],["2024-12-28",154189022.8,10025127.3],["2025-01-28",149267198.8,9233666.38],["2025-02-28",131943966.8,8465915.05],["2025-03-28",125367110.5,7781807.72],["2025-04-28",117238613.1,7127147.05],["2025-05-28",112368574.8,6509482.45],["2025-06-28",99988254.23,5914372.72],["2025-07-28",85382502.47,5381543.83],["2025-08-28",72300807.56,4921856.54],["2025-09-28",57600993.4,4521948.54],["2025-10-28",55814880.46,4193570.1],["2025-11-28",54435010.17,3874600.17],["2025-12-28",52853924.88,3562862.69],["2026-01-28",51704308.72,3259695.55],["2026-02-28",48388842.39,2962622.7],["2026-03-28",47173848.94,2683796.57],["2026-04-28",45468224.78,2411532.19],["2026-05-28",44034796.02,2148403.47],["2026-06-28",41972769.71,1893121.68],["2026-07-28",39085756.02,1648962.9],["2026-08-28",36387659.26,1420731.37],["2026-09-28",33571454.92,1206970.51],["2026-10-28",30596786.79,1008386.32],["2026-11-28",27855409.75,826711.04],["2026-12-28",24816957.49,661149.47],["2027-01-28",22278212,513593.06],["2027-02-28",16284804.59,381046.4],["2027-03-28",14216890.57,284519.48],["2027-04-28",11234067.39,200345.49],["2027-05-28",9581364.03,133877.2],["2027-06-28",7070447.52,77370.71],["2027-07-28",4364608.56,35979.93],["2027-08-28",1919297.32,10964.13]]BMW202301=Generic("BMW Auto",{"cutoff":"2022-09-30","closing":"2023-04-07","firstPay":"2023-05-26","stated":"2060-12-01","poolFreq":"MonthEnd","payFreq":["DayOfMonth",26]},{"assets":[["ProjectedCashflow",8000000000.00,"2022-09-30",projectedFlow,"MonthEnd"]],'issuanceStat':{'IssuanceBalance':8000000001.8}},(("distAcc",{"balance":0}),("cashReserve",{"balance":80_000_000.02,"type":{"fixReserve":80_000_000.02}}),("revolBuyAcc",{"balance":0})),(("A",{"balance":6_940_000_000,"rate":0.027,"originBalance":6_940_000_000.00,"originRate":0.027,"startDate":"2023-04-03","rateType":{"Fixed":0.027},"bondType":{"Sequential":None}}),("Sub",{"balance":1_060_000_002.17,"rate":0.0,"originBalance":1_060_000_002.17,"originRate":0.00,"startDate":"2023-04-03","rateType":{"Fixed":0.0},"bondType":{"Equity":None}})),(("serviceFee",{"type":{"annualPctFee":[("poolBalance",),0.01]}}),("bmwFee",{"type":{"fixFee":0}}),("admFee",{"type":{"recurFee":["MonthFirst",15000]}})),{"default":[["transfer",'revolBuyAcc',"distAcc"],["transfer",'cashReserve',"distAcc"],["payFee","distAcc",["admFee"],{"support":["account","cashReserve"]}],["payFee","distAcc",["serviceFee"],{"support":["account","cashReserve"]}],["accrueAndPayInt","distAcc",["A"]],["accrueAndPayInt","cashReserve",["A"]],["runTrigger","ExcessTrigger"]# update the trigger status during the waterfall,["If",[("trigger","InDistribution","ExcessTrigger"),False]# if it was triggered ,["transfer","distAcc",'cashReserve',{"reserve":"gap"}]]# trasnfer amt to cash reserver account,["IfElse",["status","Revolving"]# acitons in the revolving period,[["transfer","distAcc",'revolBuyAcc',{"formula":("substract",("bondBalance",),("poolBalance",))}],["buyAsset",["Current|Defaulted",1.0,0],"revolBuyAcc",None]# buy asset with 1:1 if asset with performing,["payIntResidual","distAcc","Sub"]],[["payPrin","distAcc",["A"]]# actions if deal is in Amortizing status,["payPrin","distAcc",["Sub"]],["payFeeResidual","distAcc","bmwFee"]]]],"endOfCollection":[["calcFee","serviceFee"]]# accure fee by end of collection period,"cleanUp":[["sellAsset",["Current|Defaulted",1.0,0],"distAcc"],["accrueAndPayInt","distAcc",["A"]],["payPrin","distAcc",["A"]],["payPrin","distAcc",["Sub"]],["payFeeResidual","distAcc","bmwFee"]]},(["CollectedInterest","distAcc"],["CollectedPrincipal","distAcc"],["CollectedPrepayment","distAcc"],["CollectedRecoveries","distAcc"]),None,None,None,{"BeforeDistribution":{"DefTrigger":{"condition":["any",[">=","2024-05-26"],[("cumPoolDefaultedRate",),">",0.016]],"effects":("newStatus","Amortizing"),"status":False,"curable":False}},"InDistribution":{"ExcessTrigger":{"condition":[("accountBalance","distAcc"),">",("bondBalance","A")],"effects":("newReserveBalance","cashReserve",{"fixReserve":0})# if was triggered, change reserve account amount to 0,"status":False,"curable":False}}},"Revolving"# start deal with "Revolving" status)#perf = ("Mortgage",{"CDR":0.15},{"CPR":0.0015},{"Rate":0.3,"Lag":4},None)##r = localAPI.run(BMW202301,# poolAssump = ("Pool"# ,("Mortgage",{"CDR":0.15},{"CPR":0.0015},{"Rate":0.3,"Lag":4},None)# ,None# ,None)# ,runAssump = [("inspect",("MonthEnd",("bondBalance",))# ,("MonthFirst",("poolBalance",)))# ,("interest"# ,("LPR5Y",0.05))# ,("revolving"# ,["constant"# ,["Mortgage"# ,{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30# ,"freq":"Monthly","type":"Level","originDate":"2021-02-01"}# ,{"currentBalance":2200# ,"currentRate":0.08# ,"remainTerm":20# ,"status":"current"}]]# ,perf)]# ,read=True)#r['pool']['flow']
Using variable fee rate ( a formula based rate for a formula)
fromabsboximportGenericasset=["AdjustRateMortgage",{"originBalance":73_875.00,"originRate":["floater",0.04,{"index":"USCMT1Y","spread":0.01,"reset":"YearFirst"}],"originTerm":360,"freq":"Monthly","type":"Level","originDate":"1999-05-01","arm":{"initPeriod":2,"firstCap":0.01,"periodicCap":0.01,"lifeCap":0.09}},{"currentBalance":20_788.41,"currentRate":0.0215,"remainTerm":77,"status":"current"}]GNMA_36208ALG4=Generic("820146/36208ALG4/G2-Custom AR",{"collect":["2023-05-01","2023-05-31"],"pay":["2023-05-26","2023-06-28"],"stated":"2070-01-01","poolFreq":"MonthEnd","payFreq":["DayOfMonth",20]},{'assets':[asset],'issuanceStat':{"IssuanceBalance":10000}},(("acc01",{"balance":0}),),(("A1",{"balance":20_899.37,"rate":0.025,"originBalance":1_553_836.00,"originRate":0.07,"startDate":"2020-01-03","rateType":{"floater":[0.07,"USCMT1Y",0.01,"YearFirst"],"dayCount":"DC_30_360_US"},"bondType":{"Sequential":None},"lastAccrueDate":"2023-04-30"}),),(("Ginnie_Mae_guaranty",{"type":{"annualPctFee":[("poolBalance",),0.0006]},"feeDueDate":"2023-04-26"}),("service_fee",{"type":{"annualPctFee":[("poolBalance",),("Max",("substract",("poolWaRate",),("bondRate","A1")),("constant",0))]},"feeDueDate":"2023-04-26"})),{"amortizing":[["calcFee","Ginnie_Mae_guaranty","service_fee"],["payFee","acc01",['Ginnie_Mae_guaranty',"service_fee"]],["payInt","acc01",["A1"]],["payPrin","acc01",["A1"]]],"endOfCollection":[["liqSupport","Ginnie_Mae","account","acc01",{"formula":("floorWithZero",("substract",("cumPoolDefaultedBalance",),("liqBalance","Ginnie_Mae")))}],["calcInt","A1"]]},[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],{"Ginnie_Mae":{"type":"Unlimited","start":"2023-05-26"}},None,None,None,("PreClosing","Amortizing"))if__name__=='__main__':fromabsboximportAPIlocalAPI=API("https://absbox.org/api/latest")r=localAPI.run(GNMA_36208ALG4,runAssump=[("inspect",("MonthEnd",("cumPoolDefaultedBalance",)),("MonthEnd",("liqBalance","Ginnie_Mae"))),("interest",("USCMT1Y",0.0468))],poolAssump=("Pool",("Mortgage",{"CDR":0.005},None,{"Rate":0.3,"Lag":4},None),None,None),read=True)# Inspect cumulative defaulted balancer['result']['inspect']['<CumulativePoolDefaultedBalance>']# Inspect credit provided by Ginnie Maer['result']['inspect']['<LiqBalance:Ginnie_Mae>']# the cash deposited to SPV in account `acc01`r['accounts']['acc01'][r['accounts']['acc01']["memo"]=="<Support:Ginnie_Mae>"]
set βinpsectβ during the waterfall with a comment string and a list of <Formula>
view result from ["inspect"]["waterfallInspect"]
deal_data={"name":"Inspection vars during waterfall","dates":{"cutoff":"2021-06-01","closing":"2021-07-15","firstPay":"2021-08-26","payFreq":["DayOfMonth",20],"poolFreq":"MonthEnd","stated":"2030-01-01"},"pool":{'assets':[["Mortgage",{"originBalance":2200,"originRate":["fix",0.045],"originTerm":30,"freq":"Monthly","type":"Level","originDate":"2021-02-01"},{"currentBalance":2200,"currentRate":0.08,"remainTerm":30,"status":"current"}]]},"accounts":{"acc01":{"balance":0}},"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":{"Equity":None}}},"fees":{"trusteeFee":{"type":{"fixFee":30}}},"collect":[["CollectedInterest","acc01"],["CollectedPrincipal","acc01"],["CollectedPrepayment","acc01"],["CollectedRecoveries","acc01"]],"waterfall":{"Amortizing":[["payFee","acc01",['trusteeFee']],["calcInt","A1"]# debug cashflow during the waterfall,["inspect","BeforePayInt bond:A1",("bondDueInt","A1")],["payInt","acc01",["A1"]]# debug cashflow during the waterfall,["inspect","AfterPayInt bond:A1",("bondDueInt","A1")],["payPrin","acc01",["A1"]],["payPrin","acc01",["B"]],["payPrinResidual","acc01",["B"]]]},"status":("PreClosing","Amortizing")}if__name__=="__main__":fromabsboximportAPI,mkDeallocalAPI=API("http://localhost:8081",check=False)deal=mkDeal(deal_data)r=localAPI.run(deal,poolAssump=None,runAssump=None,read=True)r['bonds']['A1']# view the waterfall inspect resultr['result']['waterfallInspect']