Source code for climada_petals.engine.cat_bonds.premium_class

import pandas as pd
import numpy as np
from pathlib import Path
from scipy.optimize import curve_fit, minimize

from climada_petals.util.config import LOGGER

# path to data folder of ibrd cat bond data
DATA_DIR = (Path(__file__).parent.parent.parent).joinpath('data/cat_bonds')


# regression coefficients for chatoro premium calculation (extracted from Chatoro et al., 2022)
b_0 = -0.5907
b_1 = 1.3986
b_2 = 2.2520
b_3 = 0.0377
b_4 = 0.4613
b_5 = -0.0239
b_6 = -2.6742
b_7 = 0.7057

[docs] class PremiumCalculations: """ Premium calculation utilities for catastrophe bond simulations. This class provides premium estimation methods based on Chatoro regression, IBRD bond curve fitting, and Sharpe ratio benchmarks. Parameters ---------- bond_simulation_class : object Bond simulation instance exposing `loss_metrics`, `df_loss_month`, and `term` attributes. """
[docs] def __init__(self, bond_simulation_class): """ Initialize with a bond simulation object. Parameters ---------- bond_simulation_class : object Bond simulation instance exposing `loss_metrics`, `df_loss_month`, and `term` attributes. """ self.bond_simulation_class = bond_simulation_class
### CHATORO-PRICING ###
[docs] def calc_chatoro_premium(self, peak_multi: int, investment_graded: int, hybrid_trigger: int, GCIndex = None, BBSpread = None): """ Calculate premium using the Chatoro et al. (2022) regression model. Parameters ---------- peak_multi : float Indicator wether the cat bond is considered peak peril or if it is covering multiple perils. If either is ture = 1, else 0. investment_graded : int Indicator for investment-grade status (e.g., 1 for yes, 0 for no). hybrid_trigger : int Indicator for hybrid trigger structure (e.g., 1 for yes, 0 for no). GCIndex : float, optional Guy Carpenter Global Property Catastrophe Rate on Line Index value. BBSpread : float, optional ICE BofA BB US High Yield Index OAS value. Sets ------- chatoro_prem_rate: float Calculated Chatoro premium rate. """ if GCIndex is None: GCIndex = 180 #Guy Carpenter Global Property Catastrophe Rate on Line Index (January, 2025) LOGGER.info(f'Using default GCIndex value of {GCIndex}') else: pass if BBSpread is None: BBSpread = 1.6 #ICE BofA BB US High Yield Index Option-Adjusted Spread (January, 2025) LOGGER.info(f'Using default BBSpread value of {BBSpread}') else: pass self.chatoro_prem_rate = (b_0 + b_1 * self.bond_simulation_class.loss_metrics['Expected_annual_loss'] * 100 + b_2 * peak_multi + b_3 * GCIndex + b_4 * BBSpread + b_5 * self.bond_simulation_class.term * 12 + b_6 * investment_graded + b_7 * hybrid_trigger) / 100 LOGGER.info(f'Calculated Chatoro premium rate: {self.chatoro_prem_rate}')
### IBRD-PRICING ###
[docs] def monoExp(self, x, a, k, b): """ Exponential function for risk multiple curve fitting. Parameters ---------- x : float or np.ndarray Input value(s) for the expected loss. a : float Amplitude parameter. k : float Decay rate parameter. b : float Offset parameter. Returns ------- float or np.ndarray Evaluated exponential function values. """ return a * np.exp(-k * x) + b
[docs] def calc_ibrd_premium(self, peril=None, year=None): """ Fits a monotonic exponential curve to catastrophe bond data for bonds issued by the World Bank to estimate premium parameters. This function loads IBRD bond data from a csv file, optionally filters the data by peril type or issuing year, and fits a monotonic exponential function to the relationship between expected loss and risk multiple. The fitted parameters are returned. Parameters ---------- peril : str, optional Peril type to filter the bonds. If None, no filtering by peril is applied. year : list[int] or int, optional Issuing year(s) to filter the bonds. If None, no filtering by year is applied. Sets ------- ibrd_prem_rate : float The estimated premium rate based on the fitted curve and the bond's expected annual loss. """ ibrd_bonds = pd.read_csv(DATA_DIR.joinpath('IBRD_bonds.csv'), sep=';') if peril is not None: flt_ibrd_bonds = ibrd_bonds[ibrd_bonds['Peril'] == peril] flt_ibrd_bonds = flt_ibrd_bonds.reset_index(drop=True) elif year is not None: if isinstance(year, int): year = [year] flt_ibrd_bonds = ibrd_bonds[ibrd_bonds['Issuing date'].isin(year)] flt_ibrd_bonds = flt_ibrd_bonds.reset_index(drop=True) else: flt_ibrd_bonds = ibrd_bonds.copy() #perform the fit params_prem_ibrd, cv = curve_fit(self.monoExp, flt_ibrd_bonds['Expected Loss'], flt_ibrd_bonds['Risk Multiple']) a, k, b = params_prem_ibrd LOGGER.info(f'Fitted IBRD premium parameters: a={a}, k={k}, b={b}') self.ibrd_prem_rate = self.monoExp(self.bond_simulation_class.loss_metrics['Expected_annual_loss']*100, a, k, b) * self.bond_simulation_class.loss_metrics['Expected_annual_loss'] LOGGER.info(f"Calculated IBRD premium rate: {self.ibrd_prem_rate}")
### BENCHMARK SHARPE RATIO PREMIUMS ### '''Benchmark pricing function -> goes through all losses and determines required premium to achieve a certain target Sharpe ratio'''
[docs] def find_sharpe(self, premium: float, monthly_losses: pd.DataFrame, target_sharpe: float): """ Calculates the squared difference between the Sharpe ratio of a cat bond cash flow and a target Sharpe ratio. The function simulates the annual cash flows of a catastrophe bond investment, adjusting for losses and premium payments. It computes the average return and standard deviation of the net cash flows, then calculates the Sharpe ratio and returns the squared difference from the target Sharpe ratio. Parameters ---------- premium : float Annual premium rate paid to the investor. monthly_losses : pandas.DataFrame DataFrame with columns 'losses' (list of loss amounts per event) and 'months' (list of event months) for each year. target_sharpe : float Target Sharpe ratio to compare against. Returns ------- float Squared difference between the calculated Sharpe ratio and the target Sharpe ratio. """ ncf = [] cur_nominal = 1 for i in range(len(monthly_losses)): losses = monthly_losses['losses'].iloc[i] months = monthly_losses['months'].iloc[i] if np.sum(losses) == 0: ncf.append(cur_nominal * premium) else: ncf_pre_event = (cur_nominal * premium) / 12 * (months[0]) ncf_post_event = [] for j in range(len(losses)): loss = losses[j] month = months[j] cur_nominal -= loss if cur_nominal < 0: loss += cur_nominal cur_nominal = 0 if j + 1 < len(losses): nex_month = months[j+1] ncf_post_event.append(((cur_nominal * premium) / 12 * (nex_month - month)) - loss) else: ncf_post_event.append(((cur_nominal * premium) / 12 * (12- month)) - loss) ncf.append(ncf_pre_event + np.sum(ncf_post_event)) if (i + 1) % self.bond_simulation_class.term == 0: cur_nominal = 1 avg_ret = np.mean(ncf) sigma = np.std(ncf) return (avg_ret / sigma - target_sharpe)**2
'''Benchmark pricing function -> wrapper function to call the optimization'''
[docs] def calc_benchmark_premium(self, target_sharpe: float): """ Calculates the initial premium required to achieve a target Sharpe ratio for a given set of annual losses. This function uses numerical optimization to find the premium value that results in the desired Sharpe ratio. Parameters ---------- target_sharpe : float Desired Sharpe ratio to be achieved. Returns ------- float Optimal premium value that achieves the target Sharpe ratio. """ result = minimize(lambda p: self.find_sharpe(p, self.bond_simulation_class.df_loss_month, target_sharpe), x0=[0.05]) self.benchmark_prem_rate = result.x[0]