The model maintains and updates a portfolio of bonds as it moves through time.
When the simulation starts, the portfolio has 10 bond invocations maturing on 2001 to 2010. After a 20 years simulation, where maturing bonds are destroyed and new bonds are purchased if the cash position allows, the portfolio is left with 3 bonds purchased in 2018, 2019 and 2020.
SWR_MatchCash1@ Begin
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Generate rates and factors - DoRandomize@ to randomize
dummy = RANDOM(Seed,DoRandomize@)
! Get rates. DoRates@ is used to trigger
Rates% = SCRIPT('GetRates', ListLink({Alpha, Beta, Sigma, Rt, Tmax, DoRates@}))
! Get A(t,T)s and B(t,Ts)
AtTs% = SCRIPT('GetAtT', Listlink({Alpha, Beta, Sigma, Lambda, Tmax, DoAtTs@}))
BtTs% = SCRIPT('GetBtT', ListLink({Alpha, Sigma, Lambda, Tmax, DoBtTs@}))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Bond continous valuation
! Bond purchase priceBondPrice = 1000
! Cash position - use list
CashList% = {Cash}
! Year of evaluation start date
SYear := DateTimeNum(StartDate,'YYYY')
! Record portfolio value
PortValue%=MAKELIST('',{1..Tmax},'NUMERIC',{PortValuse%,0})
! Write out PortfolifoTotal
Written@ eqv JOB(LOGSTATE(PortfolioTotal),'nofile write PortfolioTotal')
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Main loop - setting i triggers a yearly cycle
ForEveryYear@ EQV FORNEST({{i,1,20}},Test@,Move@,Startup@,Initialise@)
! Yearly sequence - i triggers the first, which triggers the second, etc. FORNEST waits for Test@ before it continues
UpdatedCash@ EQV SEQUENCE(UpdateCash@,LOGSTATE(i),True,False)
BondsValuated@ EQV SEQUENCE(ValuateBonds@,UpdatedCash@,True,False)
HowMany@ EQV SEQUENCE(UseSpareCash@,BondsValuated@,True,False)
BondsBought@ EQV SEQUENCE(BuyBonds@,HowMany@,True,False)
Test@ EQV SEQUENCE(UpdateMVList@,BondsBought@,True,False)!!!!!!!!!! UpdateCash@ - put FinishCash (cash position at the end of last yearly cycle) in cash
StartingCash = SCRIPT('setvargap',LISTLINK({{Cash},{FinishCash},UpdateCash@}))
!!!!!!!!!!!!! ValuateBonds@ - valuate the portfolio - valuate bonds, add coupon payments to cash position, mature and destroy matured bonds
!ValuateBonds@ EQV j = iClaimThisYear = Claims%[i]
PaidClaims@ EQV GETPUT(CashAfterClaims,CashBeforeClaims,ValuateBonds@,CashList%)
CashAfterClaims = CashBeforeClaims -ClaimThisYear
PaidClaims@ EQV j = i
ValuationDate = StartDate + j * 366! Updating the valuation dates of all the bonds triggers invocation of the subnet, where they are revaluated, etc.
EQUAL({ValuationDate}+VDs%)
!!!!!!!!!!!!!!! UseSpareCash@ - if enough cash, buy new bonds
UseSpareCash@ EQV SpareCash := Cash
! Simple buying decision logic
if SPareCash < BondPrice then CalcNumber = 0 else CalcNumber = TRUNC(SpareCash/BondPrice)
BondCost = CalcNumber * BondPrice
NewCash := Cash - BondCost
PortfolioTotal = SUM(MVList%) + NewCash
SavedCash = SCRIPT('setvargap',LISTLINK({{FinishCash},{NewCash},PortfolioTotal}))
KeepRecord = SCRIPT('setvargapInd',LISTLINK({PortValue%,{i},PortfolioTotal}))!!!!!!!!!!!!!!!!!! BuyBonds@ - create a new bond
Created@ EQV CREATENET( {BondA@},InvocName$,ParmsList%,CreateTrigger@)
IF ( BuyBonds@ AND CalcNumber > 0 ) THEN CreateTrigger@
InvocName$ = DATETIMESTR(ValuationDate,'yyyy')
CreatedMaturity = ValuationDate + 1000
ParmsList% = {{'ValuationDate',{ValuationDate}},{'MaturityDate',CreatedMaturity},{'MarketValue',CreatedMarket},{'notionalAmount',BondPrice},{'PurchasePrice',BondPrice},{'Coupon',0.05},{'NumberUnits',CalcNumber}}!!!!!!!!!!!!!!!!! UpdateMVList@ - add the new bond's mv to the MVList
MVList% = SCRIPT('InvocVars',LISTLINK({{BondA@},'MarketValue',UpdateMVList@}))
!!!!!!!!!!!!!!!! Claims structure
Claims% = {3000,3000,3000,2000,2000,2000,2000,1000,1000,1000,1000,1000,1000,10000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000}
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Cashflow matching
! Describe risks, payment patterns and treaty bases
ClaimTypes% = {'Auto', 'Fire', 'EarthQuake'}
ClaimPatterns% = {{0.8,0.2}, {0.3,0.3,0.15,0.1,0.05,0.03,0.02,0.02,0.02,0.01}, {0.5,0.3,0.2}}
ClaimTreaties% = { {10000,5000}, {10000,2000}, {5000,5000}}
ClaimsInfo% = {ClaimTypes%, ClaimPatterns%, ClaimTreaties%}! Average claim sizes - assign values to the variables and trigger DoProfile@ - produces ClaimsProfile%
AvgClaimSizes% = {ClaimType1Size, ClaimType2Size, ClaimType3Size}
ClaimsProfile% = SCRIPT('genClaimsProfile',listlink({ClaimsInfo%, AvgClaimSizes%, DoClaimsProfile@}))
MDList% = SCRIPT('InvocVars',LISTLINK({{BondA@},'MaturityDate',UpdateMDList@}))
NAList% = SCRIPT('InvocVars',LISTLINK({{BondA@},'notionalAmount',UpdateNAList@}))
NUList% = SCRIPT('InvocVars',LISTLINK({{BondA@},'NumberUnits',UpdateNUList@}))! generate expected bond maturation profile for the same period covered by the generated claims profile
MaturityProfile% = SCRIPT('genMatProfile',ListLink({VDate,Size(ClaimsProfile%), MDList%, NAList%, NUList%,DoMatProfile@}))
BondA@ subnet
! Revaluate the invoked bond
MarketValue = NumberUnits * SCRIPT( 'getBondValue' , ListLink({ VYear , MYear, @.SYear, notionalAmount, Coupon, @.Rates%, @.AtTs%, @.BtTs% } ) )
VYear := DateTimeNum(ValuationDate,'YYYY')
MYear := DateTimeNum(MaturityDate,'YYYY')
PYear := DateTimeNum(PurchaseDate,'YYYY')! Amortized cost principal
If MYear > VYear then BookValue := PurchasePrice + (VYear - PYear)/(MYear - PYear) * (notionalAmount - PurchasePrice) else BookValue := NotionalAmount
! Update lowest and highet bond value
NewLowestMarket := min( OldLowestMarket, MarketValue/ NumberUnits)
LowestDone@ EQV GETPUT(NewLowestMarket, OldLowestMarket, MarketValue, 'LowestMarket',PurchasePrice)
NewHighestMarket := max( OldHighestMarket, MarketValue/ NumberUnits)
HighestDone@ EQV GETPUT(NewHighestMarket, OldHighestMarket, MarketValue, 'HighestMarket', PurchasePrice)! Cash coupon
if VYear <= MYear then CashCoupon@
CashedCoupon@ EQV GETPUT(NewCouponCash,OldCouponCash,CashCoupon@,@.CashList%)
NewCouponCash = OldCouponCash + NumberUnits*PurchasePrice*Coupon! Cash matured bond
if CashedCoupon@ and VYear = MYear then MatureBond@
CashedBond@ EQV GETPUT(NewMaturedCash,OldMaturedCash,MatureBond@,@.CashList%)
NewMaturedCash = OldMaturedCash + NumberUnits*notionalAmount! Destroy matured bond
if CashedBond@ then DestroyBond@
Finished@ EQV SCRIPT('DestroyBond',DestroyBond@)End !BondA@
End !SWR_MatchCash1@