import%20marimo%0A%0A__generated_with%20%3D%20%220.17.6%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Example%20-%20Using%20Loman%20to%20value%20CDOs%0A%0A%20%20%20%20In%202003%2C%20Andersen%2C%20Sidenius%20and%20Basu%20published%20their%20semi-analytic%20method%20for%20CDO%20tranche%20valuation%20in%20the%20paper%20%5B%22All%20your%20hedges%20in%20one%20basket%22%5D(http%3A%2F%2Fwww.ressources-actuarielles.net%2FEXT%2FISFA%2F1226.nsf%2F0%2Fbf571acf7dbca8cbc12577b4001e3664%2F%24FILE%2FJP%2520Laurent%2520Gr%25C3%25A9gory.pdf).%20For%20homogenous%20baskets%2C%20this%20gave%20sub-second%20valuations%2C%20and%20stable%20sensitivities.%20In%20this%20example%2C%20we%20go%20through%20calculating%20the%20value%20of%20a%20CDO%20tranche%20using%20this%20method.%0A%0A%20%20%20%20First%2C%20a%20quick%20recap%20of%20the%20core%20of%20the%20method.%20Given%20a%20normally%20distributed%20random%20variable%20%24M%24%2C%20representing%20the%20state%20of%20the%20broad%20economy%2C%20and%20normally%20distributed%20random%20variable%20%24Z_i%24%2C%20representing%20the%20idiosyncratic%20performance%20of%20a%20set%20of%20indexed%20obligors%20%24i%3D1%2C%20%5Cldots%2C%20N%24%2C%20with%20%24M%24%20and%20each%20%24Z_i%24%20independent%2C%20we%20construct%20%24X_i%24%3A%0A%0A%20%20%20%20%24%24%20X_i%20%3D%20%5Csqrt%7B%5Crho%7D%20M%20%2B%20%5Csqrt%7B1-%5Crho%7D%20Z_i%20.%24%24%0A%0A%20%20%20%20We%20then%20have%20that%20each%20%24X_i%24%20is%20normally%20distributed%2C%20and%20%24%5Ctext%7BCorr%7D(X_i%2C%20X_j)%3D%5Crho%24%20where%20%24i%20%5Cne%20j%24.%0A%0A%20%20%20%20In%20a%20simulation%20context%20we%20would%20simulate%20%24M%24%20and%20each%20%24Z_i%24%2C%20and%20then%20take%20obligor%20%24i%24%20as%20defaulted%20if%20%24X_i%24%20fell%20below%20some%20threshold.%20However%2C%20conditional%20on%20%24M%24%2C%20the%20%24X_i%24s%20are%20independent%2C%20so%20given%20the%20conditional%20probability%20of%20default%2C%20calculating%20the%20loss%20distribution%20directly%20is%20a%20simple%20convolution%20and%20we%20can%20directly%20calculate%20the%20conditional%20probability%20by%0A%20%20%20%20%24%24P_%5Ctext%7Bdef%7D(i%20%7C%20M)%0A%20%20%20%20%3D%20P%5Cleft(%20X_i%20%3C%20%5CPhi%5E%7B-1%7D(P_%5Ctext%7Bdef%7D(i))%20%7C%20M%20%5Cright)%0A%20%20%20%20%3D%20%5CPhi%5Cleft(%20%5Cfrac%7B%5CPhi%5E%7B-1%7D(P_%5Ctext%7Bdef%7D(i))%20-%20%5Csqrt%7B%5Crho%7DM%7D%7B%5Csqrt%7B1-%5Crho%7D%7D%20%5Cright).%24%24%0A%0A%20%20%20%20We%20now%20start%20our%20implementation%20by%20importing%20the%20usual%20modules%2C%20and%20creating%20an%20empty%20computation.%20Our%20goal%20will%20be%20to%20calculate%20the%20PVs%20of%20the%20default%20leg%20and%20the%20coupon%20leg%20(and%20hence%20the%20fair%20spread).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20scipy%20as%20sp%0A%20%20%20%20import%20scipy.stats%0A%0A%20%20%20%20import%20loman%0A%0A%20%20%20%20norm%20%3D%20sp.stats.norm%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%0A%20%20%20%20comp%20%3D%20loman.Computation()%0A%20%20%20%20return%20comp%2C%20loman%2C%20mo%2C%20norm%2C%20np%2C%20plt%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20first%20thing%20to%20do%20is%20to%20work%20out%20the%20set%20of%20coupon%20times%2C%20so%20we%20create%20a%20node%20%22ts%22%20for%20this%20purpose.%20In%20this%20example%2C%20we%20simplify%2C%20by%20assuming%20that%20times%20can%20be%20represented%20as%20a%20number%20of%20years%2C%20and%20we%20ignore%20day%20count%20conventions%2C%20IMM%20payment%20dates%20and%20other%20details.%20To%20do%20this%2C%20we%20need%20the%20maturity%20and%20coupon%20frequency%20of%20the%20CDO.%20We%20then%20count%20back%20from%20maturity%20in%20steps%20of%201%2Ffreq.%20We%20remove%20the%20first%20coupon%20if%20the%20first%20coupon%20period%20would%20be%20too%20short%2C%20and%20we%20also%20add%20zero%2C%20although%20a%20coupon%20will%20not%20be%20paid%20at%20this%20time.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20comp.add_node(%22maturity%22%2C%20value%3D5.0)%0A%20%20%20%20comp.add_node(%22freq%22%2C%20value%3D4)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20ts(maturity%2C%20freq%2C%20long_stub%3DTrue)%3A%0A%20%20%20%20%20%20%20%20per%20%3D%201.0%20%2F%20freq%0A%20%20%20%20%20%20%20%20ts%20%3D%20list(np.r_%5Bmaturity%3A0%3A-per%5D)%0A%20%20%20%20%20%20%20%20if%20(long_stub%20and%20ts%5B-1%5D%20%3C%20per%20-%200.0001)%20or%20ts%5B-1%5D%20%3C%200.0001%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ts%20%3D%20ts%5B%3A-1%5D%0A%20%20%20%20%20%20%20%20ts.append(0.0)%0A%20%20%20%20%20%20%20%20ts.reverse()%0A%20%20%20%20%20%20%20%20return%20np.array(ts)%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.draw()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20And%20we%20can%20see%20that%20the%20vector%20of%20times%20is%20as%20we%20require%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.v.ts%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Next%2C%20as%20we%20will%20be%20calculating%20PVs%2C%20we%20need%20the%20discount%20factor%20at%20each%20of%20these%20times.%20In%20this%20simple%20example%2C%20we%20take%20a%20fixed%20interest%20rate%2C%20applied%20continuously%2C%20to%20calculate%20discount%20factors.%20We%20might%20also%20choose%20to%20use%20a%20discount%20curve%20calibrated%20in%20the%20Loman%20Interest%20Rate%20Curve%20calibration%20example.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20comp.add_node(%22r%22%2C%20value%3D0.04)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20dfs(ts%2C%20r)%3A%0A%20%20%20%20%20%20%20%20return%20np.exp(-r%20*%20ts)%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.draw()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20can%20also%20plot%20our%20wildly%20uninteresting%20discount%20factors%20to%20check%20they%20are%20reasonable%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20plt)%3A%0A%20%20%20%20plt.plot(comp.v.ts%2C%20comp.v.dfs)%0A%20%20%20%20plt.gca()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Next%2C%20we%20will%20need%20default%20probabilities%20for%20the%20obligors.%20In%20a%20more%20industrial-strength%20implementation%2C%20these%20would%20be%20calibrated%20to%20CDS%20and%20CDS%20index%20markets%2C%20but%20for%20our%20purposes%2C%20it%20is%20sufficient%20to%20use%20a%20simple%20approximation%20to%20determine%20a%20hazard%20rate%20that%20we%20will%20apply%20to%20all%20obligors.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20comp.add_node(%22spread%22%2C%20value%3D0.0060)%0A%20%20%20%20comp.add_node(%22rr%22%2C%20value%3D0.40)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20hazard_rate(spread%2C%20rr)%3A%0A%20%20%20%20%20%20%20%20return%20spread%20%2F%20(1.0%20-%20rr)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20p_defs(hazard_rate%2C%20ts)%3A%0A%20%20%20%20%20%20%20%20return%201%20-%20np.exp(-hazard_rate%20*%20ts)%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.draw()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20And%20we%20plot%20the%20default%20probability%20for%20each%20obligor%20as%20a%20function%20of%20time%2C%20to%20check%20for%20reasonableness%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20plt)%3A%0A%20%20%20%20plt.plot(comp.v.ts%2C%20comp.v.p_defs)%0A%20%20%20%20plt.gca()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20In%20this%20model%2C%20we%20will%20be%20calculating%20the%20probability%20of%20a%20set%20of%20evenly%20spaced%20discrete%20levels%20of%20loss.%20Here%2C%20we%20create%20a%20node%20losses%20where%20each%20entry%20is%20the%20amount%20of%20loss%20at%20the%20CDO%20level%20associated%20with%20each%20state.%20In%20this%20example%2C%20each%20obligor%20is%20%24%5Cfrac%7B1%7D%7B125%7D%24th%20of%20the%20portfolio%20of%20%5C%2410MM%2C%20with%20a%20recovery%20rate%20of%2040%25%2C%20so%20each%20loss%20is%20%2448k.%20We%20create%20a%20column%20vector%20for%20convenience%2C%20as%20our%20loss%20distributions%20will%20have%20a%20row%20for%20each%20loss%20level%2C%20and%20a%20column%20for%20each%20coupon%20time.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20comp.add_node(%22notional%22%2C%20value%3D10000000)%0A%20%20%20%20comp.add_node(%22n_obligors%22%2C%20value%3D125)%0A%20%20%20%20comp.add_node(%22n_losses%22%2C%20lambda%20n_obligors%3A%20n_obligors)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20lgd(rr)%3A%0A%20%20%20%20%20%20%20%20return%201.0%20-%20rr%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20losses(notional%2C%20n_losses%2C%20n_obligors%2C%20lgd)%3A%0A%20%20%20%20%20%20%20%20loss_amount%20%3D%20notional%20*%201.0%20%2F%20n_obligors%20*%20lgd%0A%20%20%20%20%20%20%20%20return%20(np.arange(n_losses)%20*%20loss_amount)%5B%3A%2C%20np.newaxis%5D%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.losses.T%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20will%20also%20need%20the%20amount%20of%20the%20tranche%20remaining%20in%20each%20state%2C%20with%20the%20attachment%20and%20detachment%20points%20provided%20as%20a%20percentage%20of%20portfolio%20notional%2C%20again%20as%20a%20column%20vector%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20comp.add_node(%22ap%22%2C%20value%3D0.03)%0A%20%20%20%20comp.add_node(%22dp%22%2C%20value%3D0.06)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20tranche_widths(notional%2C%20losses%2C%20ap%2C%20dp)%3A%0A%20%20%20%20%20%20%20%20return%20np.maximum(losses%2C%20dp%20*%20notional)%20-%20np.maximum(losses%2C%20ap%20*%20notional)%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.tranche_widths.T%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Ok%2C%20now%20we%20get%20to%20calculating%20leg%20PVs%20conditional%20on%20%24M%24.%20We%20make%20a%20node%20%60M%60%2C%20which%20is%20an%20input%20which%20we%20will%20vary%20exogenously.%20The%20first%20thing%20to%20calculate%20is%20the%20condition%20default%20probabilities%20of%20each%20obligor.%20However%2C%20in%20this%20example%2C%20every%20obligor%20has%20the%20same%20default%20probability%20and%20hence%20the%20same%20conditional%20default%20probability%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20norm%2C%20np)%3A%0A%20%20%20%20comp.add_node(%22corr%22%2C%20value%3D0.35)%0A%20%20%20%20comp.add_node(%22M%22%2C%20value%3D0)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20p_def_conds(M%2C%20p_defs%2C%20corr)%3A%0A%20%20%20%20%20%20%20%20return%20norm.cdf((norm.ppf(p_defs)%20-%20np.sqrt(corr)%20*%20M)%20%2F%20np.sqrt(1%20-%20corr))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Next%20we%20calculate%20the%20loss%20distribution%2C%20in%20a%20node%20%60ps%60.%20Rather%20than%20the%20recursion%20given%20by%20Andersen%20et%20al%2C%20we%20use%20Parcell's%20%5BLoss%20unit%20interpolation%5D(http%3A%2F%2Fwww.edparcell.com%2Flossdistint.pdf)%20method%2C%20which%20copes%20better%20with%20fractional%20losses.%20The%20node%20%60ps%60%20is%20a%20vector%20with%20dimensions%20%24%5Ctext%7Bnumber%20of%20loss%20levels%7D%20%5Ctimes%20%5Ctext%7Bnumber%20of%20coupon%20times%7D%24.%20We%20first%20initialize%20it%20so%20that%20the%20zero%20loss%20level%20has%20probability%201%20at%20each%20coupon%20time%2C%20and%20every%20other%20level%20has%20probability%200.%20We%20then%20update%20the%20loss%20distribution%20in%20a%20loop%2C%20adding%20each%20obligor%20in%20turn%2C%20and%20updating%20the%20loss%20distribution%20accordingly%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20def%20init_loss_distribution(n_losses%2C%20n_ts)%3A%0A%20%20%20%20%20%20%20%20ps%20%3D%20np.zeros((n_losses%2C%20n_ts))%0A%20%20%20%20%20%20%20%20ps%5B0%2C%20%3A%5D%20%3D%201.0%0A%20%20%20%20%20%20%20%20return%20ps%0A%0A%20%20%20%20def%20update_loss_distribution(ps%2C%20p_default%2C%20loss)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Update%20loss%20distribution%20by%20adding%20an%20obligor.%0A%0A%20%20%20%20%20%20%20%20ps%3A%20float%5Bn_losses%2C%20n_ts%5D%0A%20%20%20%20%20%20%20%20p_default%3A%20float%20%7C%20float%5Bn_ts%5D%0A%20%20%20%20%20%20%20%20loss%3A%20float.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20ps1%20%3D%20ps.copy()%0A%20%20%20%20%20%20%20%20loss_lower%20%3D%20int(loss)%0A%20%20%20%20%20%20%20%20loss_upper%20%3D%20loss_lower%20%2B%201%0A%20%20%20%20%20%20%20%20loss_frac%20%3D%20loss%20-%20loss_lower%0A%20%20%20%20%20%20%20%20ps%20*%3D%201%20-%20p_default%0A%20%20%20%20%20%20%20%20ps%5Bloss_lower%3A%5D%20%2B%3D%20p_default%20*%20(1%20-%20loss_frac)%20*%20ps1%5B%3A-loss_lower%5D%0A%20%20%20%20%20%20%20%20ps%5Bloss_upper%3A%5D%20%2B%3D%20p_default%20*%20loss_frac%20*%20ps1%5B%3A-loss_upper%5D%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20ps(n_losses%2C%20ts%2C%20n_obligors%2C%20p_def_conds)%3A%0A%20%20%20%20%20%20%20%20ps_dist%20%3D%20init_loss_distribution(n_losses%2C%20len(ts))%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(n_obligors)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20update_loss_distribution(ps_dist%2C%20p_def_conds%2C%201)%0A%20%20%20%20%20%20%20%20return%20ps_dist%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Having%20calculated%20the%20conditional%20loss%20distribution%2C%20we%20can%20see%20how%20it%20evolves%20over%20time%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20plt)%3A%0A%20%20%20%20plt.plot(comp.v.losses%5B%3A%2C%200%5D%2C%20comp.v.ps%5B%3A%2C%204%5D%2C%20label%3Df%22t%3D%7Bcomp.v.ts%5B4%5D%7D%22)%0A%20%20%20%20plt.plot(comp.v.losses%5B%3A%2C%200%5D%2C%20comp.v.ps%5B%3A%2C%208%5D%2C%20label%3Df%22t%3D%7Bcomp.v.ts%5B8%5D%7D%22)%0A%20%20%20%20plt.plot(comp.v.losses%5B%3A%2C%200%5D%2C%20comp.v.ps%5B%3A%2C%2020%5D%2C%20label%3Df%22t%3D%7Bcomp.v.ts%5B20%5D%7D%22)%0A%20%20%20%20plt.xlim(0%2C%20600000)%0A%20%20%20%20plt.legend()%0A%20%20%20%20plt.gca()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.draw()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20now%20have%20the%20probability%20of%20being%20in%20each%20loss%20level%20at%20each%20time%2C%20and%20the%20associated%20tranche%20width%20in%20that%20loss%20state%2C%20so%20we%20can%20calculate%20and%20plot%20the%20conditional%20expected%20tranche%20width%20over%20time%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np%2C%20plt)%3A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20expected_tranche_widths(ps%2C%20tranche_widths)%3A%0A%20%20%20%20%20%20%20%20return%20np.sum(ps%20*%20tranche_widths%2C%20axis%3D0)%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20plt.plot(comp.v.ts%2C%20comp.v.expected_tranche_widths)%0A%20%20%20%20plt.gca()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20conditional%20expected%20default%20leg%20is%20calculated%20as%20the%20change%20in%20conditional%20expected%20tranche%20width%20with%20discounting%20applied%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20default_amounts(expected_tranche_widths)%3A%0A%20%20%20%20%20%20%20%20return%20-np.diff(expected_tranche_widths)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20default_leg_pv(default_amounts%2C%20dfs)%3A%0A%20%20%20%20%20%20%20%20return%20np.sum(default_amounts%20*%20dfs%5B1%3A%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20make%20the%20approximation%20that%20coupon%20is%20paid%20on%20a%20notional%20that%20is%20the%20average%20of%20tranche%20width%20at%20the%20start%20and%20end%20of%20the%20coupon%20peried.%20Discounting%20is%20applied%2C%20and%20we%20calculate%20the%20PV%20of%20paying%20a%20100%25%20running%20coupon%20(!)%20for%20convenience%20calculating%20the%20fair%20spread%20later%2C%20and%20also%20the%20coupon%20paying%20a%20fixed%20level%20coupon%2C%20which%20is%20an%20input%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20average_tranche_widths(expected_tranche_widths)%3A%0A%20%20%20%20%20%20%20%20return%20(expected_tranche_widths%5B%3A-1%5D%20%2B%20expected_tranche_widths%5B1%3A%5D)%20%2F%202.0%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20coupon_leg_1_pv(average_tranche_widths%2C%20dfs%2C%20ts)%3A%0A%20%20%20%20%20%20%20%20return%20np.sum(average_tranche_widths%20*%20dfs%5B1%3A%5D%20*%20np.diff(ts))%0A%0A%20%20%20%20comp.add_node(%22coupon%22%2C%20value%3D0.05)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20coupon_leg_pv(coupon%2C%20coupon_leg_1_pv)%3A%0A%20%20%20%20%20%20%20%20return%20coupon%20*%20coupon_leg_1_pv%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20now%20have%20a%20reasonably%20complex%20graph%2C%20which%20represents%20calculating%20conditional%20default%20probability%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.draw(show_expansion%3DTrue)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20To%20calculate%20the%20(unconditional)%20leg%20PVs%2C%20we%20loop%20over%20various%20values%20of%20%24M%24%2C%20summing%20weighted%20conditional%20leg%20PVs.%20Here%20we%20use%20Gauss-Hermite%2C%20although%20other%20numerical%20integration%20schemes%2C%20from%20the%20trapezoid%20rule%20to%20adaptive%20methods%20are%20also%20possible.%20We%20would%20caution%20against%20using%20adaptive%20methods%20when%20calculating%20sensitivities%20however%2C%20as%20then%20perturbed%20results%20will%20incorporate%20changes%20in%20decisions%20made%20by%20the%20adaptive%20methods%2C%20as%20well%20as%20those%20resulting%20directly%20from%20changes%20to%20the%20inputs.%0A%0A%20%20%20%20Note%20how%20Loman%20is%20convenient%20in%20a%20couple%20of%20ways%20here.%20First%2C%20we%20do%20not%20have%20to%20worry%20about%20which%20things%20need%20to%20be%20recalculated%20and%20which%20don't%20as%20we%20change%20%24M%24.%20In%20fact%2C%20with%20extensions%20like%20stochastic%20recovery%20rates%2C%20it%20can%20become%20unclear%20which%20things%20even%20need%20to%20be%20recalculated%2C%20but%20that%20is%20automatically%20tracked.%20Second%2C%20Loman%20makes%20it%20easy%20to%20pull%20out%20multiple%20end%20or%20intermediate%20results.%20Here%20we%20extract%20three%20leg%20PVs.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20np)%3A%0A%20%20%20%20n_abscissas%20%3D%2040%0A%20%20%20%20Ms%2C%20ws%20%3D%20np.polynomial.hermite.hermgauss(n_abscissas)%0A%20%20%20%20Ms%20*%3D%20np.sqrt(2)%0A%20%20%20%20ws%20%2F%3D%20np.sqrt(np.pi)%0A%20%20%20%20default_leg_pv_uncond%20%3D%200%0A%20%20%20%20coupon_leg_1_pv_uncond%20%3D%200%0A%20%20%20%20coupon_leg_pv_uncond%20%3D%200%0A%20%20%20%20for%20M%2C%20w%20in%20zip(Ms%2C%20ws)%3A%0A%20%20%20%20%20%20%20%20comp.insert(%22M%22%2C%20M)%0A%20%20%20%20%20%20%20%20comp.compute_all()%0A%20%20%20%20%20%20%20%20default_leg_pv_uncond%20%2B%3D%20w%20*%20comp.v.default_leg_pv%0A%20%20%20%20%20%20%20%20coupon_leg_1_pv_uncond%20%2B%3D%20w%20*%20comp.v.coupon_leg_1_pv%0A%20%20%20%20%20%20%20%20coupon_leg_pv_uncond%20%2B%3D%20w%20*%20comp.v.coupon_leg_pv%0A%20%20%20%20return%20coupon_leg_1_pv_uncond%2C%20coupon_leg_pv_uncond%2C%20default_leg_pv_uncond%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20And%20below%20are%20the%20unconditional%20leg%20PVs%2C%20and%20fair%20spread%20for%20this%20tranche.%20We%20note%20that%20using%20Loman%20did%20not%20significantly%20effect%20the%20computation%20time%2C%20and%20the%20method%20remains%20efficient.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(default_leg_pv_uncond)%3A%0A%20%20%20%20default_leg_pv_uncond%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(coupon_leg_pv_uncond)%3A%0A%20%20%20%20coupon_leg_pv_uncond%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(coupon_leg_1_pv_uncond%2C%20default_leg_pv_uncond)%3A%0A%20%20%20%20default_leg_pv_uncond%20%2F%20coupon_leg_1_pv_uncond%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Finally%2C%20for%20fun%2C%20we%20use%20Loman's%20flexibility%20in%20exposing%20intermediate%20results%20to%20calculate%20an%20unconditional%20loss%20distribution%2C%20and%20plot%20how%20that%20evolves%20over%20time%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20np%2C%20plt)%3A%0A%20%20%20%20n_abscissas_dist%20%3D%2040%0A%20%20%20%20Ms_dist%2C%20ws_dist%20%3D%20np.polynomial.hermite.hermgauss(n_abscissas_dist)%0A%20%20%20%20Ms_dist%20*%3D%20np.sqrt(2)%0A%20%20%20%20ws_dist%20%2F%3D%20np.sqrt(np.pi)%0A%20%20%20%20ps_uncond%20%3D%20None%0A%20%20%20%20for%20M_dist%2C%20w_dist%20in%20zip(Ms_dist%2C%20ws_dist)%3A%0A%20%20%20%20%20%20%20%20comp.insert(%22M%22%2C%20M_dist)%0A%20%20%20%20%20%20%20%20comp.compute_all()%0A%20%20%20%20%20%20%20%20if%20ps_uncond%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ps_uncond%20%3D%20np.zeros_like(comp.v.ps)%0A%20%20%20%20%20%20%20%20ps_uncond%20%2B%3D%20comp.v.ps%20*%20w_dist%0A%0A%20%20%20%20plt.plot(comp.v.losses%5B%3A%2C%200%5D%2C%20ps_uncond%5B%3A%2C%204%5D%2C%20label%3Df%22t%3D%7Bcomp.v.ts%5B4%5D%7D%22)%0A%20%20%20%20plt.plot(comp.v.losses%5B%3A%2C%200%5D%2C%20ps_uncond%5B%3A%2C%208%5D%2C%20label%3Df%22t%3D%7Bcomp.v.ts%5B8%5D%7D%22)%0A%20%20%20%20plt.plot(comp.v.losses%5B%3A%2C%200%5D%2C%20ps_uncond%5B%3A%2C%2020%5D%2C%20label%3Df%22t%3D%7Bcomp.v.ts%5B20%5D%7D%22)%0A%20%20%20%20plt.xlim(0%2C%20600000)%0A%20%20%20%20plt.legend()%0A%20%20%20%20plt.gca()%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
e7e5fe8ca208316feb5f7459b1dd0f23e18a93fed9386183bd754add39ac8347