Financial Modelling · Section 7.1
Reinstatement premium
The premium charged for a paid reinstatement, computed from a CatXoL's claims-order erosion, pro rata to amount and to time. Income that is received only in the trials where the reinsurer pays claims, so it reduces the layer's required capital as well as the premium the reinsurer has to charge.
What a reinstatement costs the cedent
Section titled “What a reinstatement costs the cedent”A catastrophe excess of loss (CatXoL) contract with reinstatements can pay its occurrence limit times in a trial. The composition encodes that as an aggregate limit of , which reproduces every recovery and omits the cost of the restored limit. A paid reinstatement costs the cedent a reinstatement premium: when a loss uses part of the limit, the limit is restored and the cedent pays for the part restored, as a share of the contract’s premium.
Contract 1 is SunCoast’s $30M xs $10M Florida hurricane layer, with one reinstatement at 100%, pro rata to the amount reinstated, on its $14M premium. A July hurricane that causes $15M of ceded loss uses half the limit; the reinstatement restores that half, and SunCoast pays half of $14M, $7M, for it. Whatever the date of the storm, the price of the restored limit is the same.
The reinstatement premium is income, and the reinsurer’s loss stream cannot include it. The site’s year event loss table (YELT) holds positive losses only: a premium written into it as a negative loss would break the sign convention every term uses. The term produces a second stream instead, keyed by occurrence like the loss it is computed from:
The dataflow branches at the gross loss, which is why the term is not one more factor in a composition: it reads the contract’s output and does not change the loss stream. The premium stream and the loss stream are combined again only in pricing, where the reinsurer’s outcome in a trial is the claims it paid less the reinstatement premium it received.
The formal definition
Section titled “The formal definition”Index the occurrences of trial in claims order, , as the aggregate excess does, and let be the layer’s running gross after occurrence (with ). The original limit covers the first of running gross, and reinstatement 1 restores it; the first restored limit covers the next , and reinstatement 2 restores it; the last limit, the -th, is not restored. After occurrence , reinstatement restores
of the limit, and the cedent is charged the reinstatement premium
where is the contract’s premium, the premium rate of reinstatement (1 for “at 100%”), the pro rata to amount factor, and the pro rata to time factor: the share of the contract period left on the occurrence’s date if reinstatement is pro rata to time, or 1 if it is “100% as to time”. In words: for every dollar of limit an occurrence uses, and that a reinstatement restores, the cedent pays that dollar’s share of the premium, scaled by the reinstatement’s rate and, if the reinstatement is pro rata to time, by the time left.
The two factors are independent provisions of the clause, not two modes of it. Each reinstatement has its own rate and its own time provision, and a contract can combine them freely:1
| Clause | Amount factor | Time factor | Contract 1’s expected premium |
|---|---|---|---|
| Pro rata to amount, 100% as to time | 1 | $6.48M | |
| Pro rata to amount and to time | period left on the loss date | $2.25M | |
| Free reinstatement | none: | none | $0 |
Contract 1’s clause corresponds to the first row, as do Contract 4’s and Contract 6’s; Contract 6 has two reinstatements, the second at 50%. The proposed Contract 7 corresponds to the second row; Contract 6 and Contract 7 apply the two clauses.2
Every term of is proportional to , so the reinstatement premium per dollar of premium, , does not depend on the premium. Pricing works with , because there the premium is the unknown.
The running gross is the aggregate excess’s own state, tabulated for Trial 9 in the CatXoL calculation.
Contract 1 on Trial 9
Section titled “Contract 1 on Trial 9”Trial 9 is the trial whose four Florida hurricanes exhaust Contract 1’s $60M aggregate limit: $19.70M, $30M, $0 (a storm short of the attachment) and a $30M occurrence the aggregate cap clips to $10.30M, as tabulated on the CatXoL page. The reinstatement premium is computed from the running gross:
| Occurrence | Gross | Running gross | Limit restored | Premium, 100% as to time | Premium, pro rata to time |
|---|---|---|---|---|---|
| 12 July, event 10079 | $19.70M | $19.70M | $19.70M | $9.20M | $4.35M |
| 6 September, event 10007 | $30.00M | $49.70M | $10.30M | $4.80M | $1.54M |
| 6 September, event 10187 | $0 | $49.70M | $0 | $0 | $0 |
| 5 October, event 10037 | $10.30M | $60.00M | $0 | $0 | $0 |
| Trial 9 | $60.00M | $30.00M | $14.00M | $5.88M |
The July storm uses $19.70M of the original limit, all of it restored: 19.70/30 of $14M is $9.20M. The September storm uses the last $10.30M of the original limit, which the reinstatement restores, and $19.70M of the restored limit, which nothing restores. The October storm is paid out of the restored limit, and no premium is charged for it. Trial 9’s reinstatement premium is the whole $14M premium a second time, the maximum for one reinstatement at 100%. Charged pro rata to time as well, each restoration is charged only for the share of 2024 left after the storm, and Trial 9’s reinstatement premium is $5.88M.
"""Reinstatement premium: the income a paid reinstatement generates.
A CatXoL with n reinstatements can pay its occurrence limit l up to n + 1
times in a trial. The first l of ceded loss uses the original limit; each
dollar of it is restored by the first reinstatement, and a paid reinstatement
charges the cedent for the restoration. The next l uses the restored limit and
is restored by the second reinstatement, and so on. The last limit, the
(n + 1)-th, is never restored, so it generates no premium.
In claims order, with U the layer's running ceded loss after an occurrence
and U_prev the running total before it, the occurrence restores
a_m = clip(U - (m-1) l, 0, l) - clip(U_prev - (m-1) l, 0, l)
of the limit under reinstatement m, and generates
premium * sum_m beta_m * a_m / l * tau_m
of reinstatement premium: beta_m is the reinstatement's rate (1.0 for "at
100%"), a_m / l the pro rata to amount factor, and tau_m the pro rata to time
factor, the share of the contract period left on the occurrence date (tau_m = 1
for a reinstatement that is 100% as to time, like Contract 1's). Rate and time
provision are set per reinstatement.
The function takes the CatXoL's output, not the subject loss: the running
ceded loss is exactly the state the aggregate excess already tracks. The term
sorts on the occurrence key, the claims order the aggregate excess uses, and
adds one pass per reinstatement over the occurrences. The
output is a second table keyed by occurrence, holding positive premium, never
a negative loss in the loss column.
"""
import numpy as np
import pandas as pd
from caldera_re.catxol import catxol
from caldera_re.data import cedents
from caldera_re.program_ceded import suncoast_program
from caldera_re.program_pricing import MARKET
from caldera_re.report import fields, heading, table
from caldera_re.utils import OCCURRENCE_KEY, all_trials, trial_count
LIMIT = 30.0 # C1's occurrence limit, $M; one reinstatement restores it once
def reinstatement_premium(
gross: pd.DataFrame,
premium: float,
limit: float,
reinstatements: int,
rate=1.0,
pro_rata_time=False,
period_start: str = '2024-01-01',
period_end: str = '2024-12-31',
) -> pd.DataFrame:
"""Reinstatement premium per occurrence, from a CatXoL's YELT output.
Parameters
----------
gross : pd.DataFrame
The CatXoL's output rows ('gross_loss' after the aggregate cap), with
the occurrence key columns.
premium : float
The contract's premium, on which reinstatement premium is charged.
limit : float
Occurrence limit l.
reinstatements : int
Number of reinstatements n.
rate : float or sequence of float
Premium rate of each reinstatement as a fraction of the premium
(1.0 = "at 100%"); a scalar applies to all n.
pro_rata_time : bool or sequence of bool
Charge each restoration only for the share of the period left on the
occurrence date (inclusive period; the day after period_end is the
end of cover); a scalar applies to all n.
Returns one row per occurrence in claims order: the occurrence key,
'gross_loss', 'reinstated' (limit restored, $M) and
'reinstatement_premium' ($M).
"""
rates = np.broadcast_to(np.asarray(rate, dtype=float), (reinstatements,))
by_time = np.broadcast_to(np.asarray(pro_rata_time, dtype=bool), (reinstatements,))
occ = (gross.groupby(OCCURRENCE_KEY, as_index=False, sort=True)
['gross_loss'].sum())
used = occ.groupby('trial_id')['gross_loss'].cumsum().to_numpy()
before = used - occ['gross_loss'].to_numpy()
start = pd.Timestamp(period_start)
end = pd.Timestamp(period_end) + pd.Timedelta(days=1)
left = ((end - pd.to_datetime(occ['timestamp'])).dt.days.to_numpy()
/ (end - start).days)
reinstated = np.zeros(len(occ))
factor = np.zeros(len(occ))
for m, (beta, timed) in enumerate(zip(rates, by_time)): # n passes, never one per row
floor = m * limit
restored = (np.clip(used - floor, 0.0, limit)
- np.clip(before - floor, 0.0, limit))
reinstated += restored
factor += beta * restored / limit * (left if timed else 1.0)
occ['reinstated'] = reinstated
occ['reinstatement_premium'] = premium * factor
occ.attrs = dict(gross.attrs)
return occ
def per_trial(premiums: pd.DataFrame, n_trials: int | None = None) -> np.ndarray:
"""Collapse the occurrence table to one reinstatement premium per trial."""
n = trial_count(premiums, n_trials)
by_trial = premiums.groupby('trial_id')['reinstatement_premium'].sum()
return all_trials(by_trial, n).to_numpy()
def reinstatement_factor(ceded: np.ndarray, limit: float,
reinstatements: int, rate=1.0) -> np.ndarray:
"""Reinstatement premium per unit of premium, per trial: u_j.
For a clause that is pro rata to amount only, the occurrence sum
telescopes, so the trial's annual ceded loss is enough:
u_j = sum_m rate_m * clip(ceded_j - (m-1) l, 0, l) / l. Pricing uses u_j,
because the reinstatement premium is premium * u_j and the premium is the
unknown. `rate` is one rate for all n reinstatements or one per
reinstatement (C6: 100% then 50%).
"""
rates = np.broadcast_to(np.asarray(rate, dtype=float), (reinstatements,))
return sum(beta * np.clip(ceded - m * limit, 0.0, limit) / limit
for m, beta in enumerate(rates))
def expected_reinstatement_premium(c1: np.ndarray, premium: float) -> float:
"""C1's expected reinstatement premium, pro rata to amount."""
return float((premium * reinstatement_factor(c1, LIMIT, 1)).mean())
def expected_reinstatement_premium_time(premium: float) -> float:
"""C1's expected reinstatement premium under a clause also pro rata to time."""
rows = catxol(cedents.suncoast, attachment=10.0, limit=LIMIT,
reinstatements=1, peril='HU', geography_id='FL')
rp = reinstatement_premium(rows, premium, LIMIT, 1, pro_rata_time=True)
return float(per_trial(rp).mean())
if __name__ == '__main__':
rows = catxol(cedents.suncoast, attachment=10.0, limit=LIMIT,
reinstatements=1, peril='HU', geography_id='FL')
rp = reinstatement_premium(rows, MARKET['C1'], LIMIT, 1)
rp_time = reinstatement_premium(rows, MARKET['C1'], LIMIT, 1,
pro_rata_time=True)
heading("Contract 1, Trial 9: what each occurrence restores and costs")
t9 = rp[rp['trial_id'] == 9]
t9_time = rp_time.loc[t9.index, 'reinstatement_premium']
table(['Date', 'Event', 'Ceded', 'Restored', 'Premium', 'If pro rata time'],
zip(t9['timestamp'], t9['event_id'], t9['gross_loss'],
t9['reinstated'], t9['reinstatement_premium'], t9_time),
fmt=['{}', '{}', '{:.2f}M', '{:.2f}M', '{:.2f}M', '{:.2f}M'],
footer=[('Trial 9', None, t9['gross_loss'].sum(),
t9['reinstated'].sum(),
t9['reinstatement_premium'].sum(), t9_time.sum())])
c1 = suncoast_program()['C1']
by_trial = per_trial(rp)
# The occurrence route and the trial-level shortcut agree.
assert np.allclose(by_trial, MARKET['C1'] * reinstatement_factor(c1, LIMIT, 1))
print()
heading(f"Contract 1 across the 20 trials, on the ${MARKET['C1']:.0f}M premium")
fields([
('Trials in which C1 pays', f"{(c1 > 0).sum()} of {len(c1)}"),
('Expected reinstatement premium', by_trial.mean()),
('Largest in one trial', by_trial.max(), 'one full limit restored'),
('Same clause, also pro rata to time', per_trial(rp_time).mean()),
], fmt='{:.2f}M') The script computes the premium occurrence by occurrence, with and without the time factor, and prints Trial 9’s table and Contract 1’s expected reinstatement premium across the 20 trials.
Contract 6 and Contract 7 add a second reinstatement at a lower rate and a clause pro rata to time.
Two reinstatements, and a clause pro rata to time
Section titled “Two reinstatements, and a clause pro rata to time”Contract 6, Atlas Cat Fund’s $30M xs $20M Japanese earthquake layer, has two reinstatements: the first at 100%, the second at 50%, on its $13M premium. The second rate is lower because a second reinstatement is used less often: the losses in the trial have to exhaust the original limit and use part of the restored one. Across the 20 trials the first reinstatement is used in 12 trials, the second in 3:
| Contract 6, 20 trials | Reinstatement 1 (100%) | Reinstatement 2 (50%) |
|---|---|---|
| Trials in which it applies | 12 | 3 |
| Trials with a full limit restored | 9 | 1 |
| Mean share of the limit restored | 0.540 | 0.084 |
The mean share of the limit a reinstatement restores is the average, over all 20 trials, of : the whole first limit is restored in nine trials, part of it in three, and none of it in the other eight. Each reinstatement contributes its rate times its mean share to , so , and the expected reinstatement premium is . At 100% on both, the second reinstatement would add $0.55M more, $8.12M in all.
"""Two reinstatement clauses beyond Contract 1's: rates per reinstatement (C6)
and a clause pro rata to time (C7).
Contract 6 ($30M xs $20M, Japanese earthquake) has two reinstatements, the
first at 100% and the second at 50%, on its $13M premium. Reinstatement m
restores the slice of the trial's gross between (m-1) * 30 and m * 30, so its
share of the limit in trial j is clip(L_j - (m-1) * 30, 0, 30) / 30, and
u_j = 1.0 * share_1 + 0.5 * share_2, expected premium = 13 * mean(u).
Contract 7 ($20M xs $10M, Caribbean hurricane) has one reinstatement at 100%,
charged pro rata to amount and to the time left in the year on the loss date.
The occurrence-level term applies the time factor to each restoration.
"""
import numpy as np
from caldera_re.catxol import catxol
from caldera_re.data import cedents
from caldera_re.portfolio_analytics import PREMIUM_100, PROPOSED_C7
from caldera_re.portfolio_rollup import contract_gross
from caldera_re.reinstatement_premium import (
per_trial, reinstatement_factor, reinstatement_premium)
from caldera_re.report import fields, heading, table
LIMIT_C6, RATES_C6 = 30.0, (1.0, 0.5)
def band_shares(gross: np.ndarray, limit: float, n: int) -> np.ndarray:
"""Share of the limit each reinstatement restores, per trial: shape (n, N)."""
return np.array([np.clip(gross - m * limit, 0.0, limit) / limit
for m in range(n)])
if __name__ == '__main__':
c6 = contract_gross()['C6']
shares = band_shares(c6, LIMIT_C6, 2)
heading("Contract 6: share of the limit each reinstatement restores")
table(['Trial', 'Gross', 'Reinstatement 1', 'Reinstatement 2'],
[(j + 1, c6[j], shares[0, j], shares[1, j])
for j in range(len(c6)) if c6[j] > 0],
fmt=['{}', '{:.2f}M', '{:.3f}', '{:.3f}'],
footer=[('Mean (20 trials)', c6.mean(), shares[0].mean(),
shares[1].mean())])
u = reinstatement_factor(c6, LIMIT_C6, 2, RATES_C6)
assert np.allclose(u, RATES_C6[0] * shares[0] + RATES_C6[1] * shares[1])
premium = PREMIUM_100['C6']
print()
fields([
('mean(u) = 1.0 * mean share 1 + 0.5 * mean share 2', u.mean()),
], fmt='{:.3f}')
fields([
(f'Expected reinstatement premium ({premium:.0f}M * mean(u))',
premium * u.mean()),
('Same clause with both at 100%',
premium * reinstatement_factor(c6, LIMIT_C6, 2, 1.0).mean()),
], fmt='{:.2f}M')
rows = catxol(cedents.trident, attachment=10.0, limit=20.0,
reinstatements=1, peril='HU')
amount = reinstatement_premium(rows, PROPOSED_C7, 20.0, 1)
timed = reinstatement_premium(rows, PROPOSED_C7, 20.0, 1,
pro_rata_time=True)
paid = amount['reinstatement_premium'] > 0
print()
heading(f"Contract 7: each restoration on the ${PROPOSED_C7:.0f}M premium")
table(['Trial', 'Date', 'Restored', 'Pro rata to amount', 'And to time'],
zip(amount.loc[paid, 'trial_id'], amount.loc[paid, 'timestamp'],
amount.loc[paid, 'reinstated'],
amount.loc[paid, 'reinstatement_premium'],
timed.loc[paid, 'reinstatement_premium']),
fmt=['{}', '{}', '{:.2f}M', '{:.2f}M', '{:.2f}M'])
print()
fields([
('Expected, pro rata to amount', per_trial(amount).mean()),
('Expected, pro rata to amount and time', per_trial(timed).mean()),
], fmt='{:.2f}M') The script prints each trial’s shares for Contract 6 and each of Contract 7’s restorations with and without the time factor.
Contract 7, Trident Re’s $20M xs $10M Caribbean hurricane layer, is the site’s example of a clause pro rata to time (hurricane covers rarely have one, for the reason below): one reinstatement at 100%, on the proposed $6M premium, charged for the share of the year left on the loss date. Its full-limit storms occur between August and October, so each restoration is charged for about 40% of the year or less. Pro rata to amount alone, its expected reinstatement premium would be $1.72M; pro rata to time as well, it is $0.54M. Hurricane seasonality is the reason such clauses are uncommon on hurricane covers:2 the cedent pays little for a restoration late in the season.
Across the demo tier, the reinstatement premium changes the mean and the tail of Contract 1’s outcome.
Across the demo tier
Section titled “Across the demo tier”Contract 1 pays in 13 of the 20 trials of the demo tier, and in every one of them SunCoast pays reinstatement premium. The expected reinstatement premium is $6.48M on the $14M premium, and in six trials (2, 3, 6, 9, 12 and 19) it is the maximum, $14M, because the gross reaches the $30M limit. The reinsurer’s outcome on Contract 1 in trial is its gross net of the reinstatement premium, . is an outcome, not a loss: it is computed from two tables, and for some contracts it can be negative.
Contract 1's AEP curves at 100% on its $14M premium: the gross loss, and the gross less the reinstatement premium SunCoast pays in the same trial. Hover for either curve's value at a return period. The dotted lines mark the expected loss and the mean outcome.
| Contract 1, 20 trials | Gross loss | Outcome |
|---|---|---|
| Mean (EL for the gross) | $17.39M | $10.92M |
| Tail Value at Risk, TVaR(90%) | $60.00M | $46.00M |
| Capital | $42.61M | $35.08M |
Net of reinstatement premium, the mean is $6.48M lower and the outcome in each of the two 90% tail trials, Trials 9 and 19, is $14M lower; the expected loss itself is unchanged. The amount removed is not a constant: in a trial in which losses exhaust the whole limit the reinsurer receives the whole premium, and in a trial in which losses use a fraction of it, that fraction. The reduction is largest in the tail of the curve, because the trials that determine Tail Value at Risk (TVaR) are the trials in which losses exhaust the original limit.
The required capital decreases when reinstatement premium is counted, and the reinstatement premium scales with the premium, so a technical premium that counts it depends on itself.
Including reinstatement premium reduces the required capital because it reduces the outcomes in the tail, and a portfolio’s outcome in every trial is a sum of such streams: a marginal-pricing application (planned) would price a contract against the portfolio’s outcome net of reinstatement premium, and the portfolio roll-up is where those streams are summed.
Implementation and cost
Section titled “Implementation and cost”The term reads the CatXoL’s output and writes a table keyed by occurrence, with columns for the restored limit and the premium charged for it. It does not write the loss column: on this site a premium in it would be a negative loss, which the sign convention rules out. The two tables share the occurrence key and are combined by a merge or a per-trial sum.
| Property | Reinstatement premium |
|---|---|
| Input | The CatXoL’s gross, after the aggregate cap, at occurrence resolution |
| Output | One row per occurrence: key, gross, limit restored, premium |
| State | Per trial: the running gross, in claims order (the aggregate excess’s own state) |
| Parameters | , , , rate and time provision per reinstatement |
| Cost | group-sum to occurrences, an sort into claims order, then vectorised passes over the occurrences |
The running gross is the state the aggregate excess already keeps. The term sorts the occurrences on their key, the same claims order, over the occurrences (a pass over input already in that order), then adds one cumulative sum per trial and one clipped difference per reinstatement, . With at one or two in practice, the cost after the sort is linear. The term carries no state across trials, so it runs on a partition by trial like every loss term. Determining the technical premium adds more work than the term does: each iteration of the fixed point recomputes TVaR over the trials, an sort, or with a selection algorithm, and the loop needs under ten iterations here.
The loss side of the same clause is on the CatXoL page, and the premium side is applied to SunCoast’s renewal in the standalone pricing story.
Footnotes
Section titled “Footnotes”-
Richard R. Anderson and Weimin Dong, “Pricing Catastrophe Reinsurance with Reinstatement Provisions Using a Catastrophe Model”, CAS Forum, Summer 1998. Prices layers whose reinstatement premiums are pro rata to the amount of limit reinstated and, optionally, to the time remaining, treating the two as separate provisions. IRMI, “More Porridge Please: Reinstatements in Reinsurance”, describes the free-versus-paid distinction and the variations in practice. ↩
-
David R. Clark, “Basics of Reinsurance Pricing”, Casualty Actuarial Society study note, rev. 2014, p. 43. Reinstatements are available “pro-rata as to amount” and less often “pro-rata as to time”, and “relatively few contracts include reinstatements pro-rata as to time” because of the seasonal nature of catastrophes such as hurricanes. ↩ ↩2