Skip to content
Draft
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
93e924f
initial commit CAT Bonds
KaiOBerg Oct 31, 2025
f502208
change subarea building to resolution input
KaiOBerg Nov 3, 2025
2c23ff5
alter subarea creation test notebook
KaiOBerg Nov 3, 2025
3e9457e
take crs from exposure
KaiOBerg Nov 7, 2025
efe9a75
dont save exp_gdf and adjust plotting
KaiOBerg Nov 7, 2025
cbd9b94
add buffer to create subareas
KaiOBerg Nov 7, 2025
716d3a0
streamline calculations
KaiOBerg Nov 7, 2025
96c26c4
change calculation of attachment and prinipal
KaiOBerg Nov 7, 2025
cc4fdee
keep only grids with exposure point within
KaiOBerg Nov 7, 2025
16caea0
fix bugs
KaiOBerg Nov 7, 2025
6533b49
adjust calc savings
KaiOBerg Nov 7, 2025
bf54f9b
adjust test notebook to changed classes
KaiOBerg Nov 7, 2025
688027d
inititate bond simulation class
KaiOBerg Nov 7, 2025
0a35b87
delete pay_dam_subarea class
KaiOBerg Nov 7, 2025
519dcb4
update function descriptions
KaiOBerg Nov 7, 2025
8a2accf
fix bugs
KaiOBerg Nov 7, 2025
08bd04e
add total payout and damage
KaiOBerg Nov 7, 2025
3d59693
update function description
KaiOBerg Nov 7, 2025
edf6598
add net cash flow and premium simulation
KaiOBerg Nov 7, 2025
2388d7c
adjust metric naming
KaiOBerg Nov 7, 2025
7db291c
update test to classes
KaiOBerg Nov 7, 2025
f9c6cab
change function naming
KaiOBerg Nov 7, 2025
1168e62
inititate premium class
KaiOBerg Nov 7, 2025
8ef65cb
add IBRD CAT bonds data file
KaiOBerg Nov 7, 2025
6d30ad7
add ibrd based premium calculation
KaiOBerg Nov 7, 2025
fc3edc8
improve logging and fix bug chatoro
KaiOBerg Nov 7, 2025
c7d1134
fix path to data dir
KaiOBerg Nov 7, 2025
6bf627e
remove premium input
KaiOBerg Nov 7, 2025
c93b862
adjust test notebook to classes
KaiOBerg Nov 7, 2025
b820b8c
add benchmark premium calculation
KaiOBerg Nov 7, 2025
ce9952c
include all premium methods in test notebook
KaiOBerg Nov 7, 2025
0d5fbcd
remove buffer grid size
KaiOBerg Nov 7, 2025
82d185d
define variables
KaiOBerg Nov 7, 2025
d6a494c
rename bond_simulation to sng_bond_simulation
KaiOBerg Nov 7, 2025
7322e02
initialize multi country bond simulation
KaiOBerg Nov 7, 2025
d6250ff
add function to derive VaR and ES
KaiOBerg Nov 7, 2025
ac4c317
add jamaica bond
KaiOBerg Nov 9, 2025
c4d2d99
fix bugs exp and haz jamaica
KaiOBerg Nov 9, 2025
78e8b8e
test multi-country bond in notebook
KaiOBerg Nov 9, 2025
4b9c735
add minimum simulation year
KaiOBerg Nov 9, 2025
a440927
Update climada_petals/engine/cat_bonds/mlt_bond_simulation.py
KaiOBerg Nov 16, 2025
616bf60
make init loss more pythonic
KaiOBerg Nov 16, 2025
d50ceb5
change var es function
KaiOBerg Nov 16, 2025
ac20d79
adjust test notebook for confidence intervals
KaiOBerg Nov 16, 2025
50ac5ef
remove nested loop from bond simulation
KaiOBerg Nov 17, 2025
da53e8a
change fucntion description
KaiOBerg Nov 17, 2025
c02d1eb
fix type of single events in df_loss_month
KaiOBerg Nov 21, 2025
7c6458b
adjust to chanings in sng_simulation
KaiOBerg Nov 21, 2025
ad92d39
implement return simulation mlt bonds
KaiOBerg Nov 21, 2025
9c5ca13
rename funciton names
KaiOBerg Nov 21, 2025
0e95f18
init function to calculate returns with trances
KaiOBerg Nov 21, 2025
805bbe1
add required principal calculation
KaiOBerg Nov 21, 2025
d692bea
save required principal to mlt_bond class
KaiOBerg Nov 21, 2025
05a88ee
rename functions
KaiOBerg Nov 21, 2025
61802b5
make bond term function private
KaiOBerg Nov 21, 2025
2b3484b
adapt changes in mlt class to test notebook
KaiOBerg Nov 21, 2025
1d5897d
fix min year in requ_principal and initialze min_year at class init
KaiOBerg Nov 21, 2025
499bf97
add function for overlapping subareas
KaiOBerg Nov 21, 2025
0fef13e
add belize
KaiOBerg Nov 21, 2025
a53e32e
make build_subareas optional
KaiOBerg Nov 21, 2025
0b09fe5
minor adjustments
KaiOBerg Nov 21, 2025
6051337
rename class names
KaiOBerg Nov 21, 2025
d2ec6c4
initialize pooling optimization problems
KaiOBerg Nov 21, 2025
edfb591
add function to derive optimal fixed pools
KaiOBerg Nov 21, 2025
7efc9a2
move allocate tranche payout to utils
KaiOBerg Nov 24, 2025
5a29cc3
rename test notebook to tutorial
KaiOBerg Nov 24, 2025
58bf8e1
move tutorial
KaiOBerg Nov 27, 2025
9166134
initialize subareas with resolution
KaiOBerg Nov 27, 2025
e5d2d47
initiate subareas by resolution
KaiOBerg Nov 27, 2025
5abe3c1
implement pooling n pools
KaiOBerg Nov 27, 2025
d8f70a0
initialise class with optimized n pools
KaiOBerg Nov 28, 2025
fc1a62f
rename n to n_pools
KaiOBerg Nov 28, 2025
7186418
rename fct process_n to process_n_pools
KaiOBerg Nov 28, 2025
62e4808
add maximum principal wrapper function
KaiOBerg Nov 28, 2025
f5c774b
fix iterration bug
KaiOBerg Nov 28, 2025
5e14044
return whole dictionary conatining multiple class instances
KaiOBerg Nov 28, 2025
b311758
add logging to simulate_bond_pool_n
KaiOBerg Nov 28, 2025
b262e71
add max_principal pooling optimization function
KaiOBerg Nov 28, 2025
4dbea02
adapt to changes in mlt_cty_bond
KaiOBerg Nov 28, 2025
20b08fa
update funciton descriptions
KaiOBerg Nov 28, 2025
c486f9c
comment tutorial script
KaiOBerg Nov 28, 2025
ad94804
change inital guess values to percentiles of hazard intensity
KaiOBerg Nov 28, 2025
df09dd3
implement option to plug in manuall init guess
KaiOBerg Nov 28, 2025
a84e767
add function to initialize subarea class with a gdf
KaiOBerg Nov 28, 2025
d422c83
update function description
KaiOBerg Nov 28, 2025
9c4a252
move turotial to docs and create init
KaiOBerg Nov 28, 2025
e9078d4
fix file paths
KaiOBerg Nov 28, 2025
f30d74b
move explode into exp_gdf function
KaiOBerg Nov 28, 2025
fa871c8
initialize subaarea unit tests
KaiOBerg Nov 28, 2025
16ce43d
remove padding of exposure resolution
KaiOBerg Nov 28, 2025
d784b82
add 1.2 padding to exposure resolution
KaiOBerg Nov 28, 2025
b7e95b4
add testing of merging polygons
KaiOBerg Nov 28, 2025
c1f5d4d
add more test cases to merging polyons
KaiOBerg Nov 28, 2025
70cd21d
fromatting
KaiOBerg Dec 1, 2025
e5d515a
init test subarea_calculations
KaiOBerg Dec 1, 2025
9635151
clearify variable origin
KaiOBerg Dec 1, 2025
b353942
fix bug
KaiOBerg Dec 1, 2025
f2e8f90
add pay_vs_dam test function
KaiOBerg Dec 1, 2025
cd7432a
add test objective_function
KaiOBerg Dec 1, 2025
b264001
initiate test script for single country bonds
KaiOBerg Dec 1, 2025
56bee84
fix loop bug and remove simulated years
KaiOBerg Dec 1, 2025
5de50d6
test simulate loss
KaiOBerg Dec 1, 2025
a7305bd
finish tests for single country bonds
KaiOBerg Dec 1, 2025
f7aae42
formatting
KaiOBerg Dec 1, 2025
76b10bc
adjust descriptions
KaiOBerg Dec 1, 2025
f53bc2e
initialize test for premium calculations
KaiOBerg Dec 1, 2025
bcd29f7
format
KaiOBerg Dec 1, 2025
e63caaa
test benchmark sharpe ration function
KaiOBerg Dec 1, 2025
b8b65f1
formatting
KaiOBerg Dec 1, 2025
9434653
update function description
KaiOBerg Dec 1, 2025
bd5b76d
test multi_level_es function
KaiOBerg Dec 1, 2025
a3ca2a0
formatting
KaiOBerg Dec 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added climada_petals/data/cat_bonds/IBRD_bonds.xlsx
Binary file not shown.
244 changes: 244 additions & 0 deletions climada_petals/engine/cat_bonds/mlt_bond_simulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import pandas as pd
import numpy as np
import logging

from utils_cat_bonds import multi_level_es

LOGGER = logging.getLogger(__name__)

class mlt_bond_simulation:

def __init__(self, subarea_calc_list, countries_list, term, number_of_terms, tranches):
self.countries = countries_list
self.term = term
self.simulated_years = number_of_terms * term
self.tranches = tranches
self.subarea_calc = subarea_calc_list



def _prepare_data(self):
self.pay_vs_dam_dic = {}
self.principal_dic_cty = {}
min_year_list = []
for idx, cty in enumerate(self.countries):
self.pay_vs_dam_dic[cty] = self.subarea_calc[idx].pay_vs_dam
self.principal_dic_cty[cty] = self.subarea_calc[idx].principal
min_year_list.append(self.subarea_calc[idx].pay_vs_dam['year'].min())

min_year = min(min_year_list)

return min_year




'''Simulate one term of bond to derive losses'''
def init_bond_loss(self, events_per_year, principal):
'''
Simulates the expected losses and payouts for a multi-country catastrophe bond over its term.
This function iterates over each year (term) and processes event data for each country, calculating
payouts and damages based on the provided nominal values and per-country nominal allocations. It tracks
losses, damages, and payouts for each country and for the bond as a whole, and computes several summary
statistics.
Parameters
----------
self: mlt_bond_simulation
A class instance Dictionary mapping country codes to their allocated nominal values.
principal : float
The total principal value of the catastrophe bond.
events_per_year : list of pandas.DataFrame
List of DataFrames, one per year of the bond's term, each containing event data with columns:
'month', 'country_code', 'pay', and 'damage'.
Returns
-------
rel_ann_bond_losses : list of floats
List of relative annual losses (as a fraction of the total principal) for each year of the bond's term.
rel_ann_cty_losses : dict
Dictionary mapping country codes to arrays of relative annual losses for each year.
rel_bond_monthly_losses : pandas.DataFrame
DataFrame containing, for each year, the array of event payouts ('losses') and corresponding months ('months'),
both normalized by the total principal.
coverage_tot : dict
Dictionary with total payout and total damage over the bond's term: {'payout': ..., 'damage': ...}.
coverage_cty : dict
Dictionary mapping country codes to their cumulative payout and damage over the bond's term:
{country_code: {'payout': ..., 'damage': ...}, ...}.
Notes
-----
- The function assumes that the term (number of years) is inferred from the length of `events_per_year`.
- Payouts are capped by the remaining principal value for the bond and by the per-country princpal allocation.
- All losses and payouts are normalized by the total principal value before being returned.
'''
ann_loss = np.zeros(self.term)
loss_month_data = []
cur_nominal = principal
cur_nom_cty = self.principal_dic_cty.copy()
tot_damage = []
rel_ann_cty_losses = {country: np.zeros(self.term) for country in self.countries}
coverage_cty = {}
for code in self.countries:
coverage_cty[code] = {'payout': 0, 'damage': 0}

for k in range(self.term):
cty_losses_event = {country: [] for country in self.countries}
cty_damages_event = {country: [] for country in self.countries}
sum_payouts = np.zeros(len(events_per_year[k]))

if not events_per_year[k].empty:
events = events_per_year[k].sort_values(by='month')
months = events['month'].to_numpy()
cties = events['country_code'].to_numpy()
pay = events['pay'].to_numpy()
dam = events['damage'].to_numpy()

sum_payouts = np.zeros(len(events))
sum_damages = np.zeros(len(events))
for o in range(len(events)):
payout = pay[o]
cty = cties[o]
damage = dam[o]

if payout == 0 or cur_nominal == 0 or cur_nom_cty[int(cty)] == 0:
event_payout = 0
else:
event_payout = payout
cur_nom_cty[int(cty)] -= event_payout
if cur_nom_cty[int(cty)] < 0:
event_payout += cur_nom_cty[int(cty)]
cur_nom_cty[int(cty)] = 0
cur_nominal -= event_payout
if cur_nominal < 0:
event_payout += cur_nominal
cur_nominal = 0

sum_payouts[o] = event_payout
sum_damages[o] = damage
cty_losses_event[cty].append(event_payout)
cty_damages_event[cty].append(damage)
losses = np.sum(sum_payouts)
damages = np.sum(sum_damages)
for cty, cty_loss in cty_losses_event.items():
rel_ann_cty_losses[cty][k] = np.sum(cty_loss)
coverage_cty[cty]['payout'] += sum(cty_losses_event[cty])
coverage_cty[cty]['damage'] += sum(cty_damages_event[cty])
else:
losses = 0
damages = 0
months = []

ann_loss[k] = losses
tot_damage.append(damages)
loss_month_data.append((sum_payouts, months))

rel_bond_monthly_losses = pd.DataFrame(loss_month_data, columns=['losses', 'months'])

rel_ann_bond_losses = list(np.array(ann_loss) / principal)
for key in rel_ann_cty_losses.keys():
rel_ann_cty_losses[key] = rel_ann_cty_losses[key] / principal
rel_bond_monthly_losses['losses'] = rel_bond_monthly_losses['losses'].values / principal
coverage_tot = {'payout': np.sum(ann_loss), 'damage': np.sum(tot_damage)}
return rel_ann_bond_losses, rel_ann_cty_losses, rel_bond_monthly_losses, coverage_tot, coverage_cty


'''Loop over all terms of bond to derive losses'''
def init_loss_simulation(self, principal, confidence_levels=[0.95, 0.99]):
"""
Simulates expected loss and attachment probability for a multi-country catastrophe bond over simulation period.
This function aggregates event data for multiple countries over a specified simulation period, computes annual and total losses,
calculates risk metrics (Value-at-Risk and Expected Shortfall) at given confidence levels, and evaluates coverage and expected loss
shares for each country. It also computes the probability that the bond is triggered (attachment probability) and can print summary statistics.
Parameters
----------
self: mlt_bond_simulation
A class instance containing a list of countrie codes and a list of subarea_calc classes with principal values, and pay_vs_dam tables.
principal : float
The total principal value of the catastrophe bond.
confidence_levels : list, optional
List of confidence levels (floats between 0 and 1) for risk metrics calculation (default is [0.95, 0.99]).
Returns
-------
df_loss_month : pandas.DataFrame
DataFrame containing monthly relative losses for the entire bond.
loss_metrics : dict
Dictionary containing expected annual loss, annual attachment probability, payout, damage, and risk metrics (VaR and ES) at specified confidence levels for annual losses.
tot_coverage_cty : dict
Dictionary mapping each country code to its total payout, damage, coverage ratio, annual expected loss, and share of annual expected loss.
Notes
-----
- The function relies on the helper functions `init_bond_loss` and `multi_level_es` for loss simulation and risk metric calculation.
- The function expects event data to be structured such that each country's DataFrame contains a 'year' and 'month' column for filtering events.

"""

min_year = self._prepare_data()

annual_losses = []
total_losses = []
list_loss_month = []
ann_cty_losses = {cty: [] for cty in self.countries}
coverage = {'payout': 0, 'damage': 0}
self.tot_coverage_cty = {}
for cty in self.countries:
self.tot_coverage_cty[cty] = {'payout': [], 'damage': [], 'coverage': [], 'EL': 0, 'share_EL': 0}

for i in range(self.simulated_years-self.term):
events_per_year = []
for j in range(self.term):
events_per_cty = []
for cty in self.countries:
events = self.pay_vs_dam_dic[int(cty)][self.pay_vs_dam_dic[int(cty)]['year'] == (min_year+i)+j].copy()
events['country_code'] = cty
events_per_cty.append(events)
year_events_df = pd.concat(events_per_cty, ignore_index=True) if events_per_cty else pd.DataFrame()
events_per_year.append(year_events_df)

rel_ann_bond_losses, rel_ann_cty_losses, rel_bond_monthly_losses, coverage_tot, coverage_cty = self.init_bond_loss(events_per_year, principal)

list_loss_month.append(rel_bond_monthly_losses)
annual_losses.extend(rel_ann_bond_losses)
coverage['payout'] += coverage_tot['payout']
coverage['damage'] += coverage_tot['damage']

for key in coverage_cty.keys():
self.tot_coverage_cty[key]['payout'].append(coverage_cty[key]['payout'])
self.tot_coverage_cty[key]['damage'].append(coverage_cty[key]['damage'])

for key in rel_ann_cty_losses:
ann_cty_losses[key].extend(rel_ann_cty_losses[key])

self.df_loss_month = pd.concat(list_loss_month, ignore_index=True)

att_prob_ann = sum(1 for x in annual_losses if x > 0) / len(annual_losses)
exp_loss_ann = np.mean(annual_losses)

annual_losses = pd.Series(annual_losses)
total_losses = pd.Series(total_losses)

risk_metrics_annual = multi_level_es(annual_losses, confidence_levels)

for key in self.tot_coverage_cty.keys():
self.tot_coverage_cty[key]['payout'] = sum(self.tot_coverage_cty[key]['payout'])
self.tot_coverage_cty[key]['damage'] = sum(self.tot_coverage_cty[key]['damage'])
self.tot_coverage_cty[key]['coverage'] = self.tot_coverage_cty[key]['payout'] / self.tot_coverage_cty[key]['damage']
self.tot_coverage_cty[key]['EL'] = np.mean(ann_cty_losses[key])

for key in self.tot_coverage_cty:
self.tot_coverage_cty[key]['share_EL'] = self.tot_coverage_cty[key]['EL'] / exp_loss_ann



self.loss_metrics = {'EL_ann': exp_loss_ann,
'AP_ann': att_prob_ann,
'Payout': coverage['payout'],
'Damage': coverage['damage'],
'VaR_99_ann': risk_metrics_annual[0.99]['VaR'],
'VaR_95_ann': risk_metrics_annual[0.95]['VaR'],
'ES_99_ann': risk_metrics_annual[0.99]['ES'],
'ES_95_ann': risk_metrics_annual[0.95]['ES']}

LOGGER.info(f'Expected Loss = {exp_loss_ann}')
LOGGER.info(f'Attachment Probability = {att_prob_ann}')



Loading
Loading