Financial Modelling · Section 9
Portfolios
The portfolio as an actively managed object and the roll-up that combines its contracts' losses into one table, at trial resolution (a YLT, for AEP) or occurrence resolution (a portfolio YELT, for OEP), and why expected loss adds across contracts but tail risk does not.
The Programs page described how a single cedent’s placement is assembled: a coordinated set of layers on a shared exposure set. But a reinsurer does not write only one program. It writes a portfolio of programs and contracts: SunCoast’s three-layer program sits beside Pacific Mutual’s Californian earthquake program, Baltica’s European windstorm quota share, and Atlas Cat Fund’s Japanese earthquake placement.
The portfolio is where a reinsurer’s business becomes visible, and it is not a passive record. Like an investment fund’s portfolio, it is actively managed. What to write, what to renew, what to shed is a continuous business decision, made with insurance contracts where a fund manager deals in capital-market instruments. Portfolio-level metrics are the key performance indicators (KPIs) the company manages by, tactically and strategically. The written portfolio is the culmination of everything the reinsurer sells, and everything else it does (investment strategy, retrocession, sidecars, cat bonds) depends on that portfolio existing and behaving predictably. Pricing and capital decisions are not based on the portfolio alone, but every one of them uses its numbers. Before any of that management can act on numbers, though, the portfolio has to be reduced to a single loss distribution. Here the portfolio is treated as a modelling object; the roll-up is the mechanical task that produces that distribution, and the one property that makes the result interesting is that expected loss adds across contracts, but tail risk does not.
A portfolio is a bundle of bundles
Section titled “A portfolio is a bundle of bundles”Nothing structurally new appears at the portfolio level. The nesting goes one level deeper:
A term transforms a loss. A contract is a composition of terms. A program is a bundle of contracts on one exposure set, and even a placement of a single layer, like Pacific Mutual’s or Atlas’s, arrives as a program of one contract. A portfolio bundles written programs and contracts for analysis. Each level is a container for the one below, and each level’s loss is a sum of the losses one level down.
To the business a portfolio is a managed object: a set of positions whose composition is deliberately steered and whose metrics are company KPIs. To the model it is a selection: a construct that picks programs and contracts and bundles them for aggregate analysis. Reinsurers routinely run several portfolios at once, cut by business unit, by line of business, by contract period, or by binding status. The last of those separates the live bound portfolio from the live working portfolio of deals still in negotiation, which is watched to catch concentrations building up before the deals bind.1 In this site’s assessment there is only one portfolio and everything else is a slice of it. That is also how the tools behave: a slice is a filter, not a new object.
Whichever slice is selected, its loss distribution comes from the roll-up: each contract scaled to its written share, then summed.
The roll-up: participation, then sum
Section titled “The roll-up: participation, then sum”Each contract, evaluated on its cedent’s year event loss table (YELT), produces a per-trial gross loss: the loss at the 100% layer. But the reinsurer rarely writes the whole layer; it writes a participation share , placed alongside a panel of other reinsurers (why layers are split is covered in Foundations). The roll-up scales each contract’s gross loss by its participation, then sums:
For each trial , take every contract’s gross loss (the 100% layer result by definition, since compositions carry no participation), scale it by that contract’s participation (the roll-up’s own scaling step), and add the scaled losses together. Portfolio roll-up is the task of producing that sum across all trials, reducing the constituents’ loss tables to a single loss distribution for the whole selection.
The sum over at a fixed assumes that trial is the same simulated future in every
contract’s YELT. For Caldera Re it is: every cedent’s YELT is sampled from
one simulation, over one event catalog and one set of
trials, so Trial 9 in SunCoast’s YELT and Trial 9 in Baltica’s are the same simulated future.
A trial_id identifies a trial within one simulation and its trial numbering; an event_id
identifies an event within one event catalog. Two simulations (two vendors’ models, and usually
two versions of one model) number their trials independently, so equal trial ids from them do
not name the same trial: summing them adds losses from unrelated futures, and nothing in the
rows shows it. The roll-up is a sum over loss sets on one simulation basis;
portfolio analytics covers what that
requires of a production portfolio.
Why participation before the sum? Because the portfolio is Caldera Re’s own position, and Caldera Re only carries its share of each contract. Summing the 100% layers first would roll up losses that belong to the other panel members. Scale each contract to the written share, then combine. Reverse the order and the portfolio distribution is wrong by whatever the rest of the panel wrote.
Roll-up is a named task (often conflated with portfolio analytics itself) for two reasons beyond the arithmetic. Operationally, the constituents rarely sit in one system.2 Business units manage their own portfolios, sometimes on different models and in different data formats, and assembling an enterprise-wide portfolio is a reconciliation exercise before it is a sum. Computationally, the reduction is big: tens of thousands of contract YELTs across 10,000 to 1,000,000+ trials, often broken out by line of business and other exposure characteristics, reduced to one loss table. That process is I/O-bound and not instant, which is why the roll-up is often scheduled, monitored, and treated as a pipeline stage of its own,3 although the vendor pitch has moved to continuous, on-demand roll-up.4 Portfolio analytics is the business requirement; the roll-up is its prerequisite.
The sum itself can be taken per trial or per occurrence, and the two resolutions produce different tables.
Two resolutions: trial and occurrence
Section titled “Two resolutions: trial and occurrence”What the roll-up produces depends on the resolution of the sum, and the distinction matters enough to name the outputs.
Trial level → a year loss table (YLT). Sum each contract’s participated losses per trial and the
result has one row per trial: trial_id, loss. That structure is a YLT, not a YELT, because the roll-up chose to drop the occurrence detail (timestamp, event_id).
A YLT is all the aggregate exceedance probability (AEP) view needs, and it is the fast path when
aggregate metrics are the only consumers.
Occurrence level → a YELT. Keep the occurrence key, sum the participated losses per occurrence (an occurrence key shared by several cedents’ YELTs from one simulation and trial numbering names one simulated occurrence, so its losses add), and the result is a genuine portfolio YELT: the portfolio’s loss for every simulated catastrophe. This is the roll-up that supports occurrence exceedance probability (OEP) metrics (the distribution of the single worst event) and any downstream contract with occurrence terms. The constituents arrive ready for it: every contract’s output is itself a YELT, aggregate components included. The aggregate excess attributes its gross to occurrences by erosion, the per-occurrence audit trail the aggregate excess of loss (AggXoL) page walks through.
Occurrence resolution is the safer default, and this site’s recommendation: OEP metrics need occurrence losses,5 and a portfolio protected by retrocession with occurrence terms cannot be evaluated without them. The trial-level YLT remains useful twice over: as the cheap roll-up when only AEP metrics are needed, and as the simpler structure to reason about. Reducing a YELT to a YLT is lossy, irreversible, and always a choice: no financial term outputs a YLT (every term, the aggregate excess included, produces occurrence rows), so the reduction happens only where a consumer asks for it, and the trial-level roll-up is the usual place. When in doubt, roll up at occurrence resolution and collapse later.
| Trial-level roll-up → YLT | Occurrence-level roll-up → portfolio YELT | |
|---|---|---|
| Schema | trial_id, loss | trial_id, timestamp, event_id, loss (plus the enrichment columns) |
| Sum taken per | trial | occurrence: a (trial_id, timestamp, event_id) key shared by cedents’ YELTs from one simulation and trial numbering; its losses add |
| Supports | AEP curve, EL, VaR, TVaR | Everything the YLT supports, plus OEP metrics and any downstream contract with occurrence terms |
| Rows | one per portfolio occurrence (1,113 on the demo tier) | |
| Reducible to the other | No: the occurrence detail cannot be recovered | Yes: a groupby-sum per trial gives the YLT exactly |
| Use it when | Only aggregate metrics are needed, and speed matters | Retrocession, OEP, or slicing within contracts is in view (the production default) |
Caldera Re’s portfolio is rolled up at both resolutions, and the occurrence-level result collapses to the trial-level one exactly.
Caldera Re’s portfolio, rolled up
Section titled “Caldera Re’s portfolio, rolled up”Caldera Re’s six base contracts, at the shares it writes:
| Contract | Cedent | Peril · region | Participation |
|---|---|---|---|
| C1 | SunCoast | Hurricane · US (FL) | 14.5% |
| C2 | SunCoast | Earthquake · US (CA/AZ) | 20% |
| C3 | SunCoast | All-US backstop (net of C1, C2) | 10% |
| C4 | Pacific Mutual | Earthquake · US (CA) | 25% |
| C5 | Baltica | Windstorm · EU | 30% |
| C6 | Atlas Cat Fund | Earthquake · Japan | 15% |
Running the roll-up across the locked 20-trial demo tier (at both resolutions) gives the portfolio’s loss distribution, expressed as a pair of exceedance probability (EP) curves. The tail figures below rest on two trials (VaR(90%) is the second-worst of twenty and TVaR(90%) averages the worst two), so they demonstrate the roll-up’s mechanics and do not estimate a capital amount; a production roll-up runs over 10,000 to 1,000,000+ trials, and the 1,000-trial tier shows the same quotes resting on many more trials.
The portfolio's EP curves from the roll-up; hover for each curve's loss at any return period the twenty trials resolve. AEP uses the trial aggregate: the 1-in-10-year loss is $22.08M, the second-worst of twenty trials. OEP uses the largest single occurrence per trial from the occurrence-level roll-up: its 1-in-10 loss is $5.00M. The flat OEP top is the SunCoast backstop's participated share concentrating on the occurrence that pushes its aggregate layer through.
"""Caldera Re's portfolio roll-up: from six contracts to one portfolio loss distribution.
The roll-up is the mechanical prerequisite of portfolio analytics: reduce the
constituents' ceded losses, taken at each contract's participation share, to a
single portfolio loss distribution. The only genuinely new step versus the
single-program roll-up (`program_ceded.py`) is that here the constituents span
*different cedents*, so their tails land in different trials and the aggregate
diversifies harder.
Two resolutions:
Trial level (AEP) -- scale each contract's per-trial gross by p_i and
sum per trial -> a portfolio YLT (year loss
table: one loss per trial, occurrence detail gone)
Occurrence level (OEP) -- take each contract's occurrence-resolution
output (aggregate layers arrive already
erosion-attributed), scale by p_i, and sum per
occurrence -> a portfolio YELT that supports
OEP metrics and occurrence-term retrocession
Metrics (EL, VaR, TVaR) are then read straight off the portfolio's EP curves
with the same Toolkit helpers used everywhere else. Expected loss is additive
across contracts; tail risk is not -- the gap between the portfolio's TVaR and
the sum of the constituents' TVaRs is the diversification benefit.
Reproduces the numbers in the Financial Modelling *Portfolios* page and the
Applications *Portfolio analytics* story from the locked 20-trial YELT.
"""
import numpy as np
import pandas as pd
from caldera_re.aep import aep
from caldera_re.aggregate_excess import aggregate_excess
from caldera_re.aggxol import aggxol
from caldera_re.catxol import catxol
from caldera_re.data import cedents
from caldera_re.expected_loss import expected_loss
from caldera_re.loss_filter import loss_filter
from caldera_re.program_ceded import suncoast_program
from caldera_re.quota_share import quota_share
from caldera_re.contract_period import contract_period
from caldera_re.report import fields, heading, table
from caldera_re.tvar import tvar
from caldera_re.var import var
N = 20 # locked trial count of the Caldera Re demo data
ALPHA = 0.90 # confidence level for the tail metric
# Each contract carries Caldera Re's participation share. Premiums, capital,
# and pricing live in portfolio_analytics.py -- the roll-up itself is purely
# a loss computation.
CONTRACTS = {
'C1': {'cedent': 'SunCoast', 'peril': 'HU', 'region': 'US',
'participation': 0.145},
'C2': {'cedent': 'SunCoast', 'peril': 'EQ', 'region': 'US',
'participation': 0.20},
'C3': {'cedent': 'SunCoast', 'peril': 'Multi', 'region': 'US',
'participation': 0.10},
'C4': {'cedent': 'PacificMutual', 'peril': 'EQ', 'region': 'US',
'participation': 0.25},
'C5': {'cedent': 'Baltica', 'peril': 'WS', 'region': 'EU',
'participation': 0.30},
'C6': {'cedent': 'AtlasCatFund', 'peril': 'EQ', 'region': 'JP',
'participation': 0.15},
}
def _per_trial(result: pd.DataFrame, column: str = 'gross_loss') -> np.ndarray:
"""Collapse a contract result into a length-N per-trial gross array."""
arr = np.zeros(N)
for tid, value in result.groupby('trial_id')[column].sum().items():
arr[int(tid) - 1] = value
return arr
def contract_gross() -> dict:
"""Per-trial ceded loss at the 100% layer for all six base contracts.
C1-C3 come from SunCoast's inuring program (C3's subject is net of C1 and
C2 recoveries); C4-C6 are
independent single contracts on their own cedents.
"""
prog = suncoast_program()
c4 = _per_trial(catxol(cedents.pacific, attachment=3.0, limit=10.0,
reinstatements=1, peril='EQ', geography_id='CA'))
c5 = _per_trial(quota_share(cedents.baltica, cession_percentage=0.25,
peril='WS'))
c6 = _per_trial(catxol(cedents.atlas, attachment=20.0, limit=30.0,
reinstatements=2, peril='EQ', geography_id='JP'))
return {'C1': prog['C1'], 'C2': prog['C2'], 'C3': prog['C3'],
'C4': c4, 'C5': c5, 'C6': c6}
def _curve(arr: np.ndarray) -> pd.DataFrame:
"""Wrap a per-trial array as an AEP curve for the metric helpers."""
return aep(pd.DataFrame({'trial_id': range(1, N + 1), 'loss': arr}), n_trials=N)
def metrics(arr: np.ndarray, alpha: float = ALPHA) -> dict:
"""EL, VaR, and TVaR for a per-trial loss array."""
curve = _curve(arr)
return {'el': expected_loss(curve), 'var': var(curve, alpha),
'tvar': tvar(curve, alpha)}
def roll_up() -> dict:
"""Roll the six contracts into Caldera Re's portfolio.
Returns per-contract participated arrays, the portfolio array, and the
diversification decomposition (sum-of-standalone vs portfolio).
"""
gross = contract_gross()
participated = {c: gross[c] * CONTRACTS[c]['participation'] for c in gross}
portfolio = sum(participated.values())
standalone = {c: metrics(participated[c]) for c in participated}
port = metrics(portfolio)
sum_el = sum(s['el'] for s in standalone.values())
sum_tvar = sum(s['tvar'] for s in standalone.values())
return {
'gross': gross,
'participated': participated,
'portfolio': portfolio,
'standalone': standalone,
'port': port,
'sum_el': sum_el, 'sum_tvar': sum_tvar,
'diversification_tvar': sum_tvar - port['tvar'],
}
# --- Occurrence-level roll-up (OEP resolution) ------------------------------
#
# The trial-level roll-up produces a YLT -- one loss per trial -- which is all
# the AEP view needs. OEP metrics and occurrence-term retrocession need the
# portfolio's losses per *occurrence*. The contract outputs already carry that
# resolution: the aggregate excess term attributes its gross to occurrences by
# erosion (claims order), so the catalog helpers return YELTs and the roll-up
# only has to reduce each one to the occurrence key.
_KEYS = ['trial_id', 'timestamp', 'event_id']
def _by_occurrence(result: pd.DataFrame,
loss_column: str = 'gross_loss') -> pd.DataFrame:
"""Reduce a contract's YELT output to the occurrence key."""
return result.groupby(_KEYS)[loss_column].sum().reset_index()
def _occurrences(yelt: pd.DataFrame, **criteria) -> pd.DataFrame:
"""Covered per-occurrence subject losses (filter + period + group)."""
covered = contract_period(loss_filter(yelt, **criteria),
'2024-01-01', '2024-12-31')
return covered.groupby(_KEYS)['loss'].sum().reset_index()
def occurrence_gross() -> dict:
"""Per-occurrence gross at the 100% layer for all six contracts.
Each entry is a DataFrame [trial_id, timestamp, event_id, gross_loss]
whose per-trial sums reproduce contract_gross() exactly.
"""
sc = cedents.suncoast
# C1/C4/C6 (CatXoL) and C2 (AggXoL): the helpers' YELT outputs, reduced
# to the occurrence key. The aggregate components (CatXoL caps, AggXoL
# layers) are already erosion-attributed by the aggregate excess term.
c1 = _by_occurrence(catxol(sc, attachment=10.0, limit=30.0,
reinstatements=1, peril='HU',
geography_id='FL'))
c2 = _by_occurrence(aggxol(sc, attachment=15.0, limit=10.0, peril='EQ'))
c4 = _by_occurrence(catxol(cedents.pacific, attachment=3.0, limit=10.0,
reinstatements=1, peril='EQ',
geography_id='CA'))
c6 = _by_occurrence(catxol(cedents.atlas, attachment=20.0, limit=30.0,
reinstatements=2, peril='EQ',
geography_id='JP'))
# C5 (quota share): proportional, so attribution is trivial.
c5 = _occurrences(cedents.baltica, peril='WS')
c5['gross_loss'] = c5.pop('loss') * 0.25
# C3 (AggXoL net of C1 and C2): the subject per occurrence is SunCoast's
# total occurrence loss net of what C1 and C2 recovered on that occurrence,
# then the 50 xs 300 aggregate layer.
subject = _occurrences(sc)
net = (subject
.merge(c1.rename(columns={'gross_loss': 'c1'}), on=_KEYS, how='left')
.merge(c2.rename(columns={'gross_loss': 'c2'}), on=_KEYS, how='left')
.fillna({'c1': 0.0, 'c2': 0.0}))
net['loss'] = net['loss'] - net['c1'] - net['c2']
c3 = _by_occurrence(aggregate_excess(net[_KEYS + ['loss']], 300.0, 50.0))
return {'C1': c1, 'C2': c2, 'C3': c3, 'C4': c4, 'C5': c5, 'C6': c6}
def occurrence_roll_up() -> dict:
"""Roll the portfolio up at occurrence resolution -> the portfolio YELT.
Scales each contract's per-occurrence gross by its participation and sums
per occurrence (shared event_ids add across cedents). Returns the portfolio
YELT, its per-trial OEP maxima, and verifies that collapsing it to trial
level reproduces the trial roll-up exactly.
"""
occ = occurrence_gross()
parts = []
for c, df in occ.items():
scaled = df.copy()
scaled['gross_loss'] *= CONTRACTS[c]['participation']
parts.append(scaled)
yelt = (pd.concat(parts)
.groupby(_KEYS)['gross_loss'].sum().reset_index()
.rename(columns={'gross_loss': 'loss'}))
aep_by_trial = (yelt.groupby('trial_id')['loss'].sum()
.reindex(range(1, N + 1), fill_value=0.0).to_numpy())
oep_by_trial = (yelt.groupby('trial_id')['loss'].max()
.reindex(range(1, N + 1), fill_value=0.0).to_numpy())
# Consistency: the portfolio YELT collapsed per trial must equal the
# trial-level roll-up (and each contract's attribution its trial gross).
trial = roll_up()
assert np.allclose(aep_by_trial, trial['portfolio']), \
'occurrence roll-up does not collapse to the trial roll-up'
for c, df in occ.items():
per_trial = (df.groupby('trial_id')['gross_loss'].sum()
.reindex(range(1, N + 1), fill_value=0.0).to_numpy())
assert np.allclose(per_trial, trial['gross'][c]), \
f'{c} occurrence attribution does not sum to its trial gross'
return {'yelt': yelt, 'aep_by_trial': aep_by_trial,
'oep_by_trial': oep_by_trial}
def trial_occurrences(j: int) -> pd.DataFrame:
"""Trial j's occurrences in date order: one row per occurrence key, one
column per contract at Caldera Re's participation, and their total."""
occ = occurrence_gross()
columns = []
for c, df in occ.items():
rows = df[df['trial_id'] == j].set_index(_KEYS)['gross_loss']
columns.append((rows * CONTRACTS[c]['participation']).rename(c))
wide = pd.concat(columns, axis=1).fillna(0.0)
wide['total'] = wide[list(occ)].sum(axis=1)
return wide[wide['total'] > 0].sort_index().reset_index()
if __name__ == '__main__':
r = roll_up()
heading("Per-contract: 100% layer and participated ($M)")
rows = []
for c in CONTRACTS:
s100 = metrics(r['gross'][c])
s = r['standalone'][c]
rows.append((c, CONTRACTS[c]['participation'], s100['el'],
s100['tvar'], s['el'], s['tvar']))
table(['C', 'part', 'EL100', 'TVaR100', 'pEL', 'pTVaR'], rows,
fmt=['{}', '{:.1%}', '{:.2f}', '{:.2f}', '{:.2f}', '{:.2f}'])
p = r['port']
print()
fields([('Portfolio EL', p['el']),
('Portfolio VaR90', p['var']),
('Portfolio TVaR90', p['tvar'])], fmt='{:.2f}M', sep=' = ')
print(f"\nSum standalone EL = {r['sum_el']:.2f}M "
f"portfolio EL = {p['el']:.2f}M (adds exactly)")
print(f"Sum standalone TVaR = {r['sum_tvar']:.2f}M "
f"portfolio TVaR = {p['tvar']:.2f}M")
print(f"Diversification benefit (TVaR) = {r['diversification_tvar']:.2f}M "
f"({r['diversification_tvar']/r['sum_tvar']:.1%} of the sum)")
# The tail: which contracts drive the worst trials?
print()
heading("Tail structure: worst trials of the participated portfolio")
order = np.argsort(r['portfolio'])[::-1]
table(['Trial', 'Total', *CONTRACTS],
[(idx + 1, r['portfolio'][idx],
*(r['participated'][c][idx] for c in CONTRACTS))
for idx in order[:4]],
fmt=['{}'] + ['{:.2f}'] * (1 + len(CONTRACTS)))
# Occurrence-level roll-up: the portfolio YELT and its OEP view.
o = occurrence_roll_up()
om = metrics(o['oep_by_trial'])
print()
heading("Occurrence roll-up: portfolio YELT (OEP resolution)")
print(f"Portfolio YELT rows (occurrences): {len(o['yelt'])}")
fields([('OEP EL', om['el'], 'mean worst-occurrence loss'),
('OEP VaR90', om['var']),
('OEP TVaR90', om['tvar'])], fmt='{:.2f}M', sep=' = ')
worst = o['yelt'].sort_values('loss', ascending=False).head(3)
print("Largest portfolio occurrences:")
for _, row in worst.iterrows():
print(f" trial {int(row['trial_id']):2d} event {int(row['event_id'])}"
f" {row['timestamp']} {row['loss']:.2f}M")
print("(collapsing the portfolio YELT per trial reproduces the "
"trial-level roll-up exactly -- asserted above)")
# One trial, occurrence by occurrence: the worst trial's total is the
# sum of many occurrences, none of them large.
t9 = trial_occurrences(9)
print()
heading("Trial 9, occurrence by occurrence, at participation ($M)")
table(['Date', 'Event', *CONTRACTS, 'Total'],
t9[['timestamp', 'event_id', *CONTRACTS, 'total']],
fmt=['{}', '{}'] + ['{:.2f}'] * (len(CONTRACTS) + 1))
fields([('Occurrences', f"{len(t9)}"),
('Largest occurrence', t9['total'].max()),
('Trial 9 total', t9['total'].sum())], fmt='{:.2f}M') The metrics are computed from those curves with the same helpers used everywhere else on the site (nothing about a portfolio distribution is special once it exists):
| Portfolio metric (AEP basis) | Value |
|---|---|
| Expected loss (EL) | $14.59M |
| VaR(90%) | $22.08M |
| TVaR(90%) | $23.51M |
On the OEP basis the 1-in-10 single-occurrence loss is $5.00M. The occurrence-level roll-up that produced it collapses back to the trial-level numbers exactly (the code above asserts it), which is the practical consequence of “lossy in one direction only.”
The portfolio’s TVaR(90%) of $23.51M is below the $36.54M that the six contracts’ standalone TVaRs sum to, and diversification accounts for the gap.
Expected loss adds; tail risk diversifies
Section titled “Expected loss adds; tail risk diversifies”Diversification makes the rolled-up distribution more than bookkeeping. Take the six contracts’ standalone metrics (each computed on its own participated loss) and compare the sum against the portfolio’s:
Each contract's standalone value at its participation, the sum of those values, and the portfolio's own. On the TVaR(90%) view the portfolio ($23.5M) sits $13.0M below the sum of the standalone tails ($36.5M) — a 35.6% diversification benefit, because the contracts do not reach their worst losses in the same trials. Toggle to EL and the two summary bars are identical ($14.59M): expected loss adds exactly, so the entire reduction is in the tail.
Expected loss adds exactly. The six contracts’ participated ELs sum to $14.59M, the portfolio EL to the cent, because expectation is linear: trial by trial the portfolio loss is the sum of the contract losses, so the means add. Structure is invisible to EL: it does not matter which cedent, peril, or region a dollar of expected loss comes from.
The tail does not add. The contracts’ standalone TVaR(90%)s sum to $36.54M, but the portfolio’s TVaR(90%) is $23.51M, $13.02M lower,6 a 35.6% reduction. The reason is structural: each contract’s TVaR averages its own worst trials, but the portfolio’s TVaR averages the trials where the total is worst, and those are different trials. SunCoast’s Florida hurricane peaks in one trial, Baltica’s European windstorm in another, Atlas’s Japanese quake in a third. They do not all fail together, so the portfolio’s worst trial is milder than the sum of each contract’s worst trial. That gap is the diversification benefit, and it is exactly TVaR’s sub-additivity, , realised across the whole portfolio.
A reinsurer can reduce the tail further with retrocession, which takes the portfolio YELT as its subject loss.
The portfolio YELT is also a subject loss
Section titled “The portfolio YELT is also a subject loss”The portfolio YELT has the same schema as any other loss table, so it can serve as another contract’s subject loss. A reinsurer buys retrocession (reinsurance on reinsurance) for two reasons: to protect its portfolio, and to free up capacity so that more business can be written against the same capital base.7 Either way, the retro contract’s subject loss YELT may be precisely the portfolio YELT this roll-up produces. The retro layer transforms that subject loss into the retro’s gross loss (the recoveries the reinsurer collects), and netting those recoveries out of the portfolio YELT gives the portfolio’s net of retro loss. The perspectives chain (subject loss, gross loss, net loss) reappears one level up, with the whole portfolio playing the role a cedent’s exposure set played before. Resolution matters here too: a retro program with occurrence terms needs the portfolio loss at YELT resolution, not as a YLT. That is another reason the occurrence-level roll-up is the one production systems keep.
The retro program itself (its layers, its price, what it is worth to Caldera Re) is out of scope for this page. A retro program can protect one slice of the portfolio, and a flattened portfolio makes any slice’s roll-up a filter and a sum.
In practice: portfolios are flattened
Section titled “In practice: portfolios are flattened”Conceptually a portfolio is the tidy tree above: programs containing contracts containing terms. In production it is usually flattened into a single list of participated layers, each tagged with the metadata that lets you slice it back apart: cedent, peril, region, program, business unit. The tree is how humans reason about a placement; the flat list is how an engine evaluates it. The two views answer the same questions differently:
| The tree | The flat list | |
|---|---|---|
| Unit | Portfolio → program → contract → term | One participated layer per row, tagged with cedent, peril, region, program, business unit |
| How a portfolio is defined | By membership: which programs it holds | By a filter over the tags: business_unit = US and status = bound |
| How a slice is computed | Walk the subtree, then roll up | Filter the rows, then roll up |
| What it is for | Reasoning about a placement; the structure a person negotiates | Evaluating at speed; the structure an engine iterates |
| Cost of a new slice | A new subtree to maintain | A new predicate, no new object |
Both views describe the same object, and a well-built model moves between them freely: evaluate the flat list for speed, group by any tag to recover the structure. The grouping keys are also what define portfolios themselves: “the live bound US portfolio” is a filter over the flat list, not a separate object. They are what let a portfolio manager ask “how much of my tail is Californian earthquake?” without re-evaluating a single contract. The slice still needs its own roll-up over the selected constituents, but that is a filter and a sum over losses already computed, not a re-evaluation of the contracts.
The portfolio analytics story slices this portfolio by region, peril, and cedent, each slice a selection rolled up again.
What carries forward
Section titled “What carries forward”A portfolio is an actively managed selection of written programs and contracts. The roll-up (scale each contract to its participation, sum within each trial or occurrence) reduces it to one loss distribution: a YLT at trial resolution for AEP, a portfolio YELT at occurrence resolution for OEP and retrocession. Even those distributions are intermediaries: the business questions they serve are the portfolio KPIs (how much can the portfolio lose, and where is its risk concentrated). Expected loss rolls up by addition. Tail risk rolls up below addition, and the gap between the sum of the standalone tails and the combined tail is the diversification benefit, a quantity that exists only in the combined column.
That closes the Financial Modelling chapter: from one term, to a contract, to a program, to the whole portfolio. And it settles the chapter’s claim. A handful of operations on trial losses can express the loss side of the core property-cat structures (quota share, catastrophe excess of loss, aggregate excess of loss) and of programs and portfolios built from them; other structures add terms to the catalog. Five terms built three contract types; one relationship between contracts built a program; one sum built the portfolio, and the same five terms apply to its loss table again as a retro contract’s subject. What the chapter builds, the Applications chapter uses. The portfolio analytics story takes this exact portfolio and works it as a portfolio manager would: assessing its risk posture, slicing it for concentrations, and weighing its expected loss against the premium it earns. And pricing one new contract against the rolled-up portfolio, the marginal view, is marginal pricing with portfolio view (planned), the application the Applications chapter builds toward.
Footnotes
Section titled “Footnotes”-
Verisk, “Touchstone Re”, product brochure, 2025. Quantifies risk “on a global, regional, peril, and line of business level” and filters a portfolio to “treaties contained in your In-Force book of business or in the status of submitted, renewals, quoted, expired”, which is the cut by line of business and by binding status; the cut by contract period is this site’s. International Actuarial Association, Risk Book: Catastrophe Risk, Insurance Regulation Committee, October 2025, sets risk-transfer decisions “at the level of a single location or account, or at a business unit or a corporate aggregate portfolio level”. The live bound and live working names are this site’s. ↩
-
Moody’s, “Four strategies to enhance reinsurance exposure management”, February 2025. Reinsurers “routinely manage vast amounts of cedant exposure data spread across numerous databases”, so a business-wide roll-up “typically involves restoring and attaching cedant databases individually, then running accumulations on a portfolio, and combining each for an aggregate view of risk”, with multiple tools and systems making it “significantly less efficient and more time-intensive”. The piece describes exposure accumulation and attributes the scatter to cedant databases; the business-unit attribution is this site’s. ↩
-
Blesson Varghese and Andrew Rau-Chaplin, “Data Challenges in High-Performance Risk Analytics”, Risk Analytics Lab, Dalhousie University, 2013. Splits reinsurance analytics into a three-stage pipeline whose middle stage is portfolio risk management, puts a reinsurer at “tens of thousands of contracts”, finds that at that stage data “needs to be scanned over rather than randomly access[ed]”, and reports that the volume means “a weekly simulation can be performed with limited possibility for a real-time simulation”. The same paper times a single contract’s million-trial simulation at 25 seconds, which is the per-contract figure behind “not instant”. ↩
-
Moody’s, “Four strategies to enhance reinsurance exposure management”, on the vendor pitch: a cloud-native platform gives reinsurers “24/7 access to real-time analytics” and lets exposure-based rollups “be carried out more frequently”. It is a product claim, and it evidences what is marketed rather than what reinsurers do. ↩
-
International Actuarial Association, Risk Book: Catastrophe Risk, October 2025. Catastrophe models “typically generate two types of EP curves”, the occurrence distribution being “the probabilities of losses from the maximum occurrence in a year”, and where the underlying loss data “is developed in a year, event loss format” the aggregate view is “a simple aggregation exercise” of it, while estimating the aggregate distribution by other routes is “more challenging to estimate and less robust than the OEP distribution”. ↩
-
The difference is taken on the unrounded tails and then displayed to two decimals; the two displayed figures differ by $13.03M. See the rounding convention in the Caldera Re appendix. ↩
-
Guy Carpenter, Glossary of Reinsurance Terms, Retrocession: it “is placed to afford additional capacity to the original reinsurer or to contain or reduce the original reinsurer’s risk of loss”. RAA, Fundamentals of Reinsurance and Glossary of Reinsurance Terms, records that reinsurers buy retrocession “in the same forms and for the same reasons as ceding insurers”, among them increased capacity, which it explains through the surplus a company commits as it writes more business. ↩