def issue_cat_bond(self, time, categ_id, per_value_per_period_premium=0): # premium is for usual reinsurance contracts paid using per value market premium # for the quasi-contract for the cat bond, nothing is paid, everything is already paid at the beginning. #per_value_reinsurance_premium = self.np_reinsurance_premium_share * risk["periodized_total_premium"] * risk["runtime"] / risk["value"] #TODO: rename this to per_value_premium in insurancecontract.py to avoid confusion """ create catbond """ total_value, avg_risk_factor, number_risks, periodized_total_premium = self.characterize_underwritten_risks_by_category( time, categ_id) if number_risks > 0: risk = { "value": total_value, "category": categ_id, "owner": self, #"identifier": uuid.uuid1(), "insurancetype": 'excess-of-loss', "number_risks": number_risks, "deductible_fraction": self.np_reinsurance_deductible_fraction, "excess_fraction": self.np_reinsurance_excess_fraction, "periodized_total_premium": 0, "runtime": 12, "expiration": time + 12, "risk_factor": avg_risk_factor } # TODO: make runtime into a parameter _, _, var_this_risk, _ = self.riskmodel.evaluate([], self.cash, risk) per_period_premium = per_value_per_period_premium * risk["value"] total_premium = sum([ per_period_premium * ((1 / (1 + self.interest_rate))**i) for i in range(risk["runtime"]) ]) # TODO: or is it range(1, risk["runtime"]+1)? #catbond = CatBond(self.simulation, per_period_premium) catbond = CatBond( self.simulation, per_period_premium, self.interest_rate ) # TODO: shift obtain_yield method to insurancesimulation, thereby making it unnecessary to drag parameters like self.interest_rate from instance to instance and from class to class """add contract; contract is a quasi-reinsurance contract""" contract = ReinsuranceContract(catbond, risk, time, 0, risk["runtime"], \ self.default_contract_payment_period, \ expire_immediately=self.simulation_parameters["expire_immediately"], \ initial_VaR=var_this_risk, \ insurancetype=risk["insurancetype"]) # per_value_reinsurance_premium = 0 because the insurance firm does not continue to make payments to the cat bond. Only once. catbond.set_contract(contract) """sell cat bond (to self.simulation)""" self.simulation.receive_obligation(var_this_risk, self, time, 'bond') catbond.set_owner(self.simulation) """hand cash over to cat bond such that var_this_risk is covered""" obligation = { "amount": var_this_risk + total_premium, "recipient": catbond, "due_time": time, "purpose": 'bond' } self.pay(obligation) #TODO: is var_this_risk the correct amount? """register catbond""" self.simulation.accept_agents("catbond", [catbond], time=time)
def issue_cat_bond(self, time: int, categ_id: int, per_value_per_period_premium: int = 0): """Method to issue cat bond to given firm for given category. Accepts: time: Type Integer. categ_id: Type Integer. per_value_per_period_premium: Type Integer. No return values. Method is only called by increase_capacity_by_category method when CatBond prices are lower than reinsurance. It then creates the CatBond as a quasi-reinsurance contract that is paid for immediately (by simulation) with no premium payments.""" [total_value, avg_risk_factor, number_risks, _,] = self.underwritten_risk_characterisation[categ_id] if number_risks > 0: # TODO: make runtime into a parameter risk = genericclasses.RiskProperties( value=total_value, category=categ_id, owner=self, insurancetype="excess-of-loss", number_risks=number_risks, deductible_fraction=self.np_reinsurance_deductible_fraction, limit_fraction=self.np_reinsurance_limit_fraction, periodized_total_premium=0, runtime=12, expiration=time + 12, risk_factor=avg_risk_factor,) _, _, var_this_risk, _ = self.riskmodel.evaluate([], self.cash, risk) per_period_premium = per_value_per_period_premium * risk.value total_premium = sum( [per_period_premium * ((1 / (1 + self.interest_rate)) ** i) for i in range(risk.runtime)]) # catbond = CatBond(self.simulation, per_period_premium) # TODO: shift obtain_yield method to insurancesimulation, thereby making it unnecessary to drag # parameters like self.interest_rate from instance to instance and from class to class new_catbond = catbond.CatBond(self.simulation, per_period_premium, self.interest_rate) """add contract; contract is a quasi-reinsurance contract""" contract = ReinsuranceContract(new_catbond, risk, time, 0, risk.runtime, self.default_contract_payment_period, expire_immediately=self.simulation_parameters["expire_immediately"], initial_var=var_this_risk, insurancetype=risk.insurancetype,) # per_value_reinsurance_premium = 0 because the insurance firm make only one payment to catbond new_catbond.set_contract(contract) """sell cat bond (to self.simulation)""" self.simulation.receive_obligation(var_this_risk, self, time, "bond") new_catbond.set_owner(self.simulation) """hand cash over to cat bond such that var_this_risk is covered""" obligation = genericclasses.Obligation(amount=var_this_risk + total_premium, recipient=new_catbond, due_time=time, purpose="bond",) self._pay(obligation) # TODO: is var_this_risk the correct amount? """register catbond""" self.simulation.add_agents(catbond.CatBond, "catbond", [new_catbond])
def process_newrisks_reinsurer( self, reinrisks_per_categ, number_reinrisks_categ, time ): #This method processes one by one the reinrisks contained in reinrisks_per_categ in order to decide whether they should be underwritten or not. #It is done in this way to maintain the portfolio as balanced as possible. For that reason we process risk[C1], risk[C2], risk[C3], risk[C4], risk[C1], risk[C2], ... and so forth. for iterion in range(max(number_reinrisks_categ)): for categ_id in range( self.simulation_parameters["no_categories"] ): #Here we take only one risk per category at a time to achieve risk[C1], risk[C2], risk[C3], risk[C4], risk[C1], risk[C2], ... if possible. if iterion < number_reinrisks_categ[ categ_id] and reinrisks_per_categ[categ_id][ iterion] is not None: risk_to_insure = reinrisks_per_categ[categ_id][iterion] underwritten_risks = [{"value": contract.value, "category": contract.category, \ "risk_factor": contract.risk_factor, "deductible": contract.deductible, \ "excess": contract.excess, "insurancetype": contract.insurancetype, \ "runtime_left": (contract.expiration - time)} for contract in self.underwritten_contracts if contract.insurancetype == "excess-of-loss"] accept, cash_left_by_categ, var_this_risk, self.excess_capital = self.riskmodel.evaluate( underwritten_risks, self.cash, risk_to_insure ) # TODO: change riskmodel.evaluate() to accept new risk to be evaluated and to account for existing non-proportional risks correctly -> DONE. if accept: per_value_reinsurance_premium = self.np_reinsurance_premium_share * risk_to_insure[ "periodized_total_premium"] * risk_to_insure[ "runtime"] / risk_to_insure[ "value"] # TODO: rename this to per_value_premium in insurancecontract.py to avoid confusion [ condition, cash_left_by_categ ] = self.balanced_portfolio( risk_to_insure, cash_left_by_categ, None ) #Here it is check whether the portfolio is balanced or not if the reinrisk (risk_to_insure) is underwritten. Return True if it is balanced. False otherwise. if condition: contract = ReinsuranceContract(self, risk_to_insure, time, per_value_reinsurance_premium, risk_to_insure["runtime"], \ self.default_contract_payment_period, \ expire_immediately=self.simulation_parameters[ "expire_immediately"], \ initial_VaR=var_this_risk, \ insurancetype=risk_to_insure[ "insurancetype"]) # TODO: implement excess of loss for reinsurance contracts self.underwritten_contracts.append(contract) self.cash_left_by_categ = cash_left_by_categ reinrisks_per_categ[categ_id][iterion] = None not_accepted_reinrisks = [] for categ_id in range(self.simulation_parameters["no_categories"]): for reinrisk in reinrisks_per_categ[categ_id]: if reinrisk is not None: not_accepted_reinrisks.append(reinrisk) return reinrisks_per_categ, not_accepted_reinrisks
def process_newrisks_insurer(self, risks_per_categ, number_risks_categ, acceptable_by_category, var_per_risk_per_categ, cash_left_by_categ, time): #This method processes one by one the risks contained in risks_per_categ in order to decide whether they should be underwritten or not. #It is done in this way to maintain the portfolio as balanced as possible. For that reason we process risk[C1], risk[C2], risk[C3], risk[C4], risk[C1], risk[C2], ... and so forth. _cached_rvs = self.contract_runtime_dist.rvs() for iter in range(max(number_risks_categ)): for categ_id in range(len(acceptable_by_category)): #Here we take only one risk per category at a time to achieve risk[C1], risk[C2], risk[C3], risk[C4], risk[C1], risk[C2], ... if possible. if iter < number_risks_categ[categ_id] and acceptable_by_category[categ_id] > 0 and \ risks_per_categ[categ_id][iter] is not None: risk_to_insure = risks_per_categ[categ_id][iter] if risk_to_insure.get("contract") is not None and risk_to_insure[ "contract"].expiration > time: # risk_to_insure["contract"]: # required to rule out contracts that have exploded in the meantime [condition, cash_left_by_categ] = self.balanced_portfolio(risk_to_insure, cash_left_by_categ, None) #Here it is check whether the portfolio is balanced or not if the reinrisk (risk_to_insure) is underwritten. Return True if it is balanced. False otherwise. if condition: contract = ReinsuranceContract(self, risk_to_insure, time, \ self.simulation.get_reinsurance_market_premium(), risk_to_insure["expiration"] - time, \ self.default_contract_payment_period, \ expire_immediately=self.simulation_parameters[ "expire_immediately"], ) self.underwritten_contracts.append(contract) self.cash_left_by_categ = cash_left_by_categ risks_per_categ[categ_id][iter] = None # TODO: move this to insurancecontract (ca. line 14) -> DONE # TODO: do not write into other object's properties, use setter -> DONE else: [condition, cash_left_by_categ] = self.balanced_portfolio(risk_to_insure, cash_left_by_categ, var_per_risk_per_categ) #Here it is check whether the portfolio is balanced or not if the risk (risk_to_insure) is underwritten. Return True if it is balanced. False otherwise. if condition: contract = InsuranceContract(self, risk_to_insure, time, self.simulation.get_market_premium(), \ _cached_rvs, \ self.default_contract_payment_period, \ expire_immediately=self.simulation_parameters[ "expire_immediately"], \ initial_VaR=var_per_risk_per_categ[categ_id]) self.underwritten_contracts.append(contract) self.cash_left_by_categ = cash_left_by_categ risks_per_categ[categ_id][iter] = None acceptable_by_category[categ_id] -= 1 # TODO: allow different values per risk (i.e. sum over value (and reinsurance_share) or exposure instead of counting) not_accepted_risks = [] for categ_id in range(len(acceptable_by_category)): for risk in risks_per_categ[categ_id]: if risk is not None: not_accepted_risks.append(risk) return risks_per_categ, not_accepted_risks
def iterate(self, time): # TODO: split function so that only the sequence of events remains here and everything else is in separate methods """obtain investments yield""" self.obtain_yield(time) """realize due payments""" self.effect_payments(time) if isleconfig.verbose: print(time, ":", self.id, len(self.underwritten_contracts), self.cash, self.operational) self.make_reinsurance_claims(time) """mature contracts""" if isleconfig.verbose: print("Number of underwritten contracts ", len(self.underwritten_contracts)) maturing = [contract for contract in self.underwritten_contracts if contract.expiration <= time] for contract in maturing: self.underwritten_contracts.remove(contract) contract.mature(time) contracts_dissolved = len(maturing) """effect payments from contracts""" [contract.check_payment_due(time) for contract in self.underwritten_contracts] if self.operational: """request risks to be considered for underwriting in the next period and collect those for this period""" new_risks = [] if self.is_insurer: new_risks += self.simulation.solicit_insurance_requests(self.id, self.cash) if self.is_reinsurer: new_risks += self.simulation.solicit_reinsurance_requests(self.id, self.cash) contracts_offered = len(new_risks) try: assert contracts_offered > 2 * contracts_dissolved except: print("Something wrong; agent {0:d} receives too few new contracts {1:d} <= {2:d}".format(self.id, contracts_offered, 2*contracts_dissolved),file=sys.stderr) #print(self.id, " has ", len(self.underwritten_contracts), " & receives ", contracts_offered, " & lost ", contracts_dissolved) new_nonproportional_risks = [risk for risk in new_risks if risk.get("insurancetype")=='excess-of-loss' and risk["owner"] is not self] new_risks = [risk for risk in new_risks if risk.get("insurancetype") in ['proportional', None] and risk["owner"] is not self] underwritten_risks = [{"value": contract.value, "category": contract.category, \ "risk_factor": contract.risk_factor, "deductible": contract.deductible, \ "excess": contract.excess, "insurancetype": contract.insurancetype, \ "runtime": contract.runtime} for contract in self.underwritten_contracts if contract.reinsurance_share != 1.0] """deal with non-proportional risks first as they must evaluate each request separatly, then with proportional ones""" for risk in new_nonproportional_risks: accept, var_this_risk, self.excess_capital = self.riskmodel.evaluate(underwritten_risks, self.cash, risk) # TODO: change riskmodel.evaluate() to accept new risk to be evaluated and to account for existing non-proportional risks correctly -> DONE. if accept: per_value_reinsurance_premium = self.np_reinsurance_premium_share * risk["periodized_total_premium"] * risk["runtime"] / risk["value"] #TODO: rename this to per_value_premium in insurancecontract.py to avoid confusion contract = ReinsuranceContract(self, risk, time, per_value_reinsurance_premium, risk["runtime"], \ self.default_contract_payment_period, \ expire_immediately=self.simulation_parameters["expire_immediately"], \ initial_VaR=var_this_risk, \ insurancetype=risk["insurancetype"]) # TODO: implement excess of loss for reinsurance contracts self.underwritten_contracts.append(contract) #pass # TODO: write this nonproportional risk acceptance decision section based on commented code in the lines above this -> DONE. """obtain risk model evaluation (VaR) for underwriting decisions and for capacity specific decisions""" # TODO: Enable reinsurance shares other tan 0.0 and 1.0 expected_profit, acceptable_by_category, var_per_risk_per_categ, self.excess_capital = self.riskmodel.evaluate(underwritten_risks, self.cash) # TODO: resolve insurance reinsurance inconsistency (insurer underwrite after capacity decisions, reinsurers before). # This is currently so because it minimizes the number of times we need to run self.riskmodel.evaluate(). # It would also be more consistent if excess capital would be updated at the end of the iteration. """handle adjusting capacity target and capacity""" max_var_by_categ = self.cash - self.excess_capital self.adjust_capacity_target(max_var_by_categ) actual_capacity = self.increase_capacity(time, max_var_by_categ) # seek reinsurance #if self.is_insurer: # # TODO: Why should only insurers be able to get reinsurance (not reinsurers)? (Technically, it should work) --> OBSOLETE # self.ask_reinsurance(time) # # TODO: make independent of insurer/reinsurer, but change this to different deductable values """handle capital market interactions: capital history, dividends""" self.cash_last_periods = [self.cash] + self.cash_last_periods[:3] self.adjust_dividends(time, actual_capacity) self.pay_dividends(time) """make underwriting decisions, category-wise""" #if expected_profit * 1./self.cash < self.profit_target: # self.acceptance_threshold = ((self.acceptance_threshold - .4) * 5. * self.acceptance_threshold_friction) / 5. + .4 #else: # self.acceptance_threshold = (1 - self.acceptance_threshold_friction * (1 - (self.acceptance_threshold - .4) * 5.)) / 5. + .4 growth_limit = max(50, 2 * len(self.underwritten_contracts) + contracts_dissolved) if sum(acceptable_by_category) > growth_limit: acceptable_by_category = np.asarray(acceptable_by_category) acceptable_by_category = acceptable_by_category * growth_limit / sum(acceptable_by_category) acceptable_by_category = np.int64(np.round(acceptable_by_category)) not_accepted_risks = [] for categ_id in range(len(acceptable_by_category)): categ_risks = [risk for risk in new_risks if risk["category"] == categ_id] new_risks = [risk for risk in new_risks if risk["category"] != categ_id] categ_risks = sorted(categ_risks, key = lambda risk: risk["risk_factor"]) i = 0 if isleconfig.verbose: print("InsuranceFirm underwrote: ", len(self.underwritten_contracts), " will accept: ", acceptable_by_category[categ_id], " out of ", len(categ_risks), "acceptance threshold: ", self.acceptance_threshold) while (acceptable_by_category[categ_id] > 0 and len(categ_risks) > i): #\ #and categ_risks[i]["risk_factor"] < self.acceptance_threshold): if categ_risks[i].get("contract") is not None: #categ_risks[i]["reinsurance"]: if categ_risks[i]["contract"].expiration > time: # required to rule out contracts that have exploded in the meantime #print("ACCEPTING", categ_risks[i]["contract"].expiration, categ_risks[i]["expiration"], categ_risks[i]["identifier"], categ_risks[i].get("contract").terminating) contract = ReinsuranceContract(self, categ_risks[i], time, \ self.simulation.get_market_premium(), categ_risks[i]["expiration"] - time, \ self.default_contract_payment_period, \ expire_immediately=self.simulation_parameters["expire_immediately"], ) self.underwritten_contracts.append(contract) #categ_risks[i]["contract"].reincontract = contract # TODO: move this to insurancecontract (ca. line 14) -> DONE # TODO: do not write into other object's properties, use setter -> DONE assert categ_risks[i]["contract"].expiration >= contract.expiration, "Reinsurancecontract lasts longer than insurancecontract: {0:d}>{1:d} (EXPIRATION2: {2:d} Time: {3:d})".format(contract.expiration, categ_risks[i]["contract"].expiration, categ_risks[i]["expiration"], time) #else: # pass else: contract = InsuranceContract(self, categ_risks[i], time, self.simulation.get_market_premium(), \ self.contract_runtime_dist.rvs(), \ self.default_contract_payment_period, \ expire_immediately=self.simulation_parameters["expire_immediately"], \ initial_VaR = var_per_risk_per_categ[categ_id]) self.underwritten_contracts.append(contract) acceptable_by_category[categ_id] -= 1 # TODO: allow different values per risk (i.e. sum over value (and reinsurance_share) or exposure instead of counting) i += 1 not_accepted_risks += categ_risks[i:] not_accepted_risks = [risk for risk in not_accepted_risks if risk.get("contract") is None] # return unacceptables #print(self.id, " now has ", len(self.underwritten_contracts), " & returns ", len(not_accepted_risks)) self.simulation.return_risks(not_accepted_risks) #not implemented #"""adjust liquidity, borrow or invest""" #pass self.estimated_var()
def reinsure_tranche( self, category: int, deductible_fraction: float, limit_fraction: float, time: int, purpose: str, ): (total_value, avg_risk_factor, number_risks, periodized_total_premium) = ( self.underwritten_risk_characterisation[category].total_value, self.underwritten_risk_characterisation[category].avg_risk_factor, self.underwritten_risk_characterisation[category].number_risks, self.underwritten_risk_characterisation[category]. periodized_total_premium, ) runtime = isleconfig.simulation_parameters[ "reinsurance_contract_runtime"] risk = genericclasses.RiskProperties( value=total_value, category=category, owner=self, insurancetype="excess-of-loss", number_risks=number_risks, deductible_fraction=deductible_fraction, limit_fraction=limit_fraction, periodized_total_premium=periodized_total_premium, runtime=runtime, expiration=time + runtime, risk_factor=avg_risk_factor, deductible=deductible_fraction * total_value, limit=limit_fraction * total_value, ) if not (risk.deductible_fraction < risk.limit_fraction <= 1): raise ValueError( "Can't reinsure invalid tranche - deductible must be < limit <= 1" ) reinsurance_type = self.decide_reinsurance_type(risk) if reinsurance_type == "reinsurance": if purpose == "newrisk": self.simulation.append_reinrisks(risk) return None elif purpose == "rollover": return risk elif reinsurance_type == "catbond": # The whole premium is transfered to the bond at creation, not periodically # TODO: Should the premium be periodic as for any other reinsurance? Would help, probably # TODO: Allow for catbonds to be paid out multiple times risk.periodized_total_premium = 0 total_premium = (self.get_catbond_price(risk) * risk.value * self.np_reinsurance_premium_share) if not self.cash >= total_premium: # We can't actually afford to issue the catbond. Ideally this shouldn't be reached, but it is. return None per_period_premium = total_premium / risk.runtime new_catbond = catbond.CatBond(self.simulation, per_period_premium, self) """add contract; contract is a quasi-reinsurance contract""" # This automatically adds reinsurance to self.reinsurance_profile # per_value_reinsurance_premium = 0 because the insurance firm make only one payment to catbond contract = ReinsuranceContract( new_catbond, risk, time, 0, risk.runtime, self.default_contract_payment_period, expire_immediately=self. simulation_parameters["expire_immediately"], insurancetype=risk.insurancetype, ) new_catbond.set_contract(contract) """sell cat bond (to self.simulation)""" # amount changed from var_this_risk to total exposure exposure = risk.value * (risk.limit_fraction - risk.deductible_fraction) self.simulation.receive_obligation(exposure + 1, new_catbond, time, "bond") new_catbond.set_owner(self.simulation) """hand cash over to cat bond to cover the premium payouts""" # If we added this as an obligation in the normal way, there would be a risk that the firm would go under # before paying, which would cause the catbond to never really have been created which is confusing obligation = genericclasses.Obligation( amount=total_premium, recipient=new_catbond, due_time=time, purpose="bond", ) self._pay(obligation) """register catbond""" self.simulation.add_agents(catbond.CatBond, "catbond", [new_catbond]) else: # print(f"\nIF {self.id} attempted to get reinsurance for {risk.limit-risk.deductible:.0f} xs" # f" {risk.deductible:.0f} but it was too expensive") return None