# Portfolio analytics

> Portfolio analytics for Caldera Re: tail metrics after the roll-up, concentrations, expected loss against premium, and the diversification benefit as a number.

Source: https://reinsurance.dev/applications/portfolio-analytics/

## The business problem

The [risk-profiling](https://reinsurance.dev/applications/risk-profiling/) and
[standalone-pricing](https://reinsurance.dev/applications/standalone-pricing/) applications each covered a single deal: the post-model report characterised one cedent's losses; one program's marketed premium was compared
with its technical premium. Both deliberately left the rest of **Caldera Re**'s portfolio out of scope. The portfolio manager answers questions about everything Caldera Re has written, not about any one contract:

> What is the risk posture of the **whole portfolio**: how much can it lose, where is that risk
> concentrated, and is it earning enough premium for its risk?

The portfolio manager reports the answers, advises underwriters about gaps and opportunities in the portfolio, and runs pro forma analyses of speculative future business to see how the portfolio's metrics would change.

That is **portfolio analytics**: it produces the metrics, views, and quantified relationships
from which the business makes informed strategic and tactical decisions. Its prerequisite is
the [roll-up](https://reinsurance.dev/financial-modelling/portfolios/), the mechanical task that reduces the portfolio's losses to one distribution. The pipeline starts with that roll-up; the analytics built
on it are the objective. One claim from standalone pricing is still untested. With C1's reinstatement
premium counted, SunCoast's program returns 9.0% on its program capital, below Caldera Re's 10%
cost of capital; the claim is that business like it returns more on capital once it is combined with
uncorrelated business, because it adds far less to the portfolio's capital than it requires alone.
Testing that claim means rolling up the six contracts' losses into one distribution, measuring it, slicing the portfolio, and settling whether the portfolio, not any single deal, earns its cost of capital on the same basis.

:::note[In Practice]
The work described here is **[exposure management](https://reinsurance.dev/glossary/#exposure-management)**: the control of catastrophe risk at the level
of the whole portfolio rather than the individual placement.[^exposure-mgmt] Job titles vary
(Portfolio Manager, Head of Cat Modelling, Head of Exposure Management among them); this
site calls the role portfolio manager throughout. The chief risk officer (CRO) and chief underwriting officer (CUO)
rarely do the operational work, but they own the responsibility for the portfolio and steer
it strategically, with the portfolio manager carrying out that strategy and keeping the business within its limits. What all these
arrangements share is the scope: the portfolio's aggregate risk and return, not individual
placements. The org chart is in the [personas](https://reinsurance.dev/foundations/personas/) chapter; portfolio analytics is
one part of the job.
:::

Every figure the portfolio manager reads is a metric of one rolled-up distribution, so
the first two steps of [the pipeline](https://reinsurance.dev/applications/portfolio-analytics/#the-pipeline) produce it.

## The pipeline

Portfolio analytics runs as a pipeline of five steps, and every one reuses methods from the earlier chapters. Only the scope is new. The pipeline is deliberately generic: nothing in
it is specific to Caldera Re, and it applies to a slice of the portfolio exactly as to the
whole.

```mermaid
flowchart LR
    A["1 · Select constituents<br/>at participation"]:::Contract --> B["2 · Roll-up<br/>Σ per occurrence"]:::Losses
    B --> C["3 · Portfolio metrics<br/>EL · VaR · TVaR · K"]:::Metric
    C --> D["4 · Slice<br/>a narrower selection"]:::Metric
    D -. repeat per subportfolio .-> A
    C --> E["5 · Risk vs opportunity<br/>EL vs premium"]:::Process

    classDef Contract fill:#BFDBF8,stroke:#4F84BA,stroke-width:1.5px,color:#2B2620
    classDef Losses fill:#F5CEBC,stroke:#B16C4B,stroke-width:1.5px,color:#2B2620
    classDef Metric fill:#BDE2CB,stroke:#459269,stroke-width:1.5px,color:#2B2620
    classDef Process fill:#F7CBCB,stroke:#B46669,stroke-width:1.5px,color:#2B2620
```

| Step | What happens | Built in |
| --- | --- | --- |
| 1. Select the constituents | Define the portfolio: choose contracts (by business unit, line of business, binding status) at the reinsurer's participations | [Portfolios](https://reinsurance.dev/financial-modelling/portfolios/) |
| 2. Roll up | Sum the participated losses per occurrence and per trial into the portfolio's loss distribution | [Portfolios](https://reinsurance.dev/financial-modelling/portfolios/) |
| 3. Measure the risk posture | EL, VaR, TVaR, capital on the portfolio distribution (the diversification [implicit in the portfolio view](https://reinsurance.dev/applications/portfolio-analytics/#the-diversification-carried-implicitly)) | [Metrics](https://reinsurance.dev/toolkit/metrics/) |
| 4. Slice | Repeat the pipeline on narrower selections to find concentrations | this page |
| 5. Weigh risk vs opportunity | Expected loss and capital cost against booked premium, per portfolio | this page |

Every later number is calculated for whichever contracts, at whichever shares, are in
[the selection](https://reinsurance.dev/applications/portfolio-analytics/#step-1-select-the-constituents).

## Step 1: select the constituents

A portfolio is a [selection](https://reinsurance.dev/financial-modelling/portfolios/), and to start with we select
everything: Caldera Re's whole enterprise portfolio (the six base contracts, each written at a
participation share placed alongside a panel of other reinsurers). The same pipeline would apply unchanged to a narrower selection (one
business unit's contracts, one line of business, only bound deals). The risk-profiling and standalone-pricing applications covered the three
SunCoast layers in detail; here they are three of six, alongside Pacific Mutual, Baltica,
and Atlas Cat Fund:

*[Table: Portfolio table. See the web page: https://reinsurance.dev/applications/portfolio-analytics/]*

The premium is Caldera Re's own share: the 100% layer premium scaled by the participation,
and the losses are scaled the same way. Caldera Re books \$12.88M of premium for its participation in these six
contracts, and on C1, C4 and C6 it also receives [reinstatement premium](https://reinsurance.dev/financial-modelling/beyond-loss/reinstatement-premium/) in the trials where they pay. The risk taken on in return for that premium is the subject of the
remaining steps, starting with [the roll-up](https://reinsurance.dev/applications/portfolio-analytics/#step-2-roll-it-up), which produces the loss distribution every
later step is computed from.

## Step 2: roll it up

The [roll-up](https://reinsurance.dev/financial-modelling/portfolios/) is one operation: for each trial, take every
contract's gross loss at Caldera Re's participation (the amount ceded to Caldera Re) and add them
together. Do it across all 20 trials of the [demo tier](https://reinsurance.dev/toolkit/trials/#demo-scale) and
the roll-up produces the portfolio's loss distribution.

*[Interactive figure: Portfolio rollup chart. See the web page: https://reinsurance.dev/applications/portfolio-analytics/]*

The peaks are staggered across trials. The portfolio's worst trial (trial 9, at
\$24.9M) is dominated by Florida hurricane: C1 pays its full \$8.7M capped share and
Baltica's windstorm contributes a further \$7.7M, while the SunCoast All-US backstop does not
contribute at all.

*[Interactive figure: Portfolio trial occurrences. See the web page: https://reinsurance.dev/applications/portfolio-analytics/]*

Trial 9's \$24.94M is the sum of 24 occurrences between February and December. The largest,
Atlas's Japanese earthquake on 9 September, is \$4.50M at Caldera Re's share; C1's \$8.7M
comes from three hurricanes, and Baltica's windstorm share from eleven storms. No occurrence in
Trial 9 exceeds \$5M, and no occurrence in any trial does, which is why the portfolio's
[occurrence exceedance probability (OEP) curve](https://reinsurance.dev/applications/portfolio-analytics/#step-3-the-risk-posture) stays at or below
\$5.00M while its trial totals reach \$24.94M.

Trial 4's loss, by contrast, is mostly C6's (a \$7.0M Japanese-quake share) with
only a small loss from C1. No single trial contains every contract's worst loss. Atlantic hurricane, European windstorm, and Japanese earthquake are treated as
uncorrelated perils,[^region-correlation] so the probability that each peril's rare large events
fall in the same trial is very small, and the simulation reflects it.[^windstorm]
This separation across trials is quantified as a capital number in [Step 3](https://reinsurance.dev/applications/portfolio-analytics/#step-3-the-risk-posture).

[^region-correlation]: [Commission Delegated Regulation (EU) 2015/35](https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:32015R0035), *Official Journal of the European Union* L 12, 17 January 2015. Article 120(2) aggregates the windstorm, earthquake, flood, hail, and subsidence capital requirements as the square root of the sum of their squares, with no correlation term, where Article 121(1) writes the same form for windstorm regions with an explicit correlation matrix. Aggregating without that term treats the five perils as uncorrelated. The Annex V windstorm matrix sets 0,00 between many separated regions and reaches 1,00 only for the three Caribbean overseas regions. The standard formula fixes a calibration rather than reporting a measured correlation, and it covers EU perils and regions, so it supports treating separated catastrophe regions and distinct perils as independent by convention rather than by measurement.

[^windstorm]: A European windstorm is not entirely independent of the Atlantic weather
    systems that drive hurricanes, but the correlation is small enough to ignore for
    simplicity.

## Step 3: the risk posture

With the portfolio's losses reduced to one distribution, its [exceedance probability (EP) curve](https://reinsurance.dev/toolkit/distributions/) and [metrics](https://reinsurance.dev/toolkit/metrics/) are computed by exactly the method the Toolkit defines: a portfolio distribution is another loss distribution. The [aggregate exceedance probability (AEP)](https://reinsurance.dev/glossary/#aep-aggregate-exceedance-probability) curve uses trial totals, which the capital metrics read; the [occurrence exceedance probability (OEP)](https://reinsurance.dev/glossary/#oep-occurrence-exceedance-probability) curve uses each trial's largest occurrence:

*Figure (interactive on the web page): The portfolio's EP curves from the roll-up; hover for each curve's loss at any return period the twenty trials resolve. AEP is the trial-aggregate view the capital metrics below read from: the 1-in-10-year loss is \$22.08M, and the dotted line marks the expected loss. The AEP curve is above it over most of its width. OEP is the largest single occurrence per trial. Its 1-in-10 loss is \$5.00M.*

| Portfolio metric (AEP) | Value | What it says |
| --- | ---: | --- |
| Expected loss (EL) | \$14.59M | The portfolio's loss in an average trial |
| Probability of a loss | 100% | At least one contract pays a claim in every trial |
| $P(L \geq \text{EL})$ | 60% | A worse-than-average trial, 12 of the 20 |
| VaR(90%) | \$22.08M | The 1-in-10-year loss, exceeded in one trial in ten |
| TVaR(90%) | \$23.51M | The average of the worst 10% of trials |
| [Capital](https://reinsurance.dev/glossary/#capital-requirement) $K = \text{TVaR} - \text{EL}$ | \$8.92M | The unexpected loss, against which capital is held |

The capital row is the risk posture as a balance-sheet figure. Premium covers the
[expected loss (EL)](https://reinsurance.dev/glossary/#el-expected-loss); capital is held against the
[unexpected loss](https://reinsurance.dev/applications/standalone-pricing/#step-3-from-metrics-to-premium),
[Tail Value at Risk (TVaR)](https://reinsurance.dev/glossary/#tvar-tail-value-at-risk) at 90% less EL, $K = \text{TVaR}(90\%) - \text{EL}$: the average excess over expected loss across the worst
10% of trials. The same definition holds for a portfolio because expected loss adds. A portfolio of well-priced contracts earns enough premium to cover the portfolio's EL, and capital is
held against the portfolio's own tail beyond it.

This is the risk posture of the portfolio: Caldera Re expects \$14.6M of claims a year on it, and a worse-than-average trial is not rare, occurring in 12 of 20. A 1-in-10 year costs \$22M,
and the reinsurer must hold \$8.9M of capital against the tail beyond the expected loss. Every one
of these is computed from the portfolio EP curve above, by the method risk profiling and standalone pricing applied to a single cedent, now on the whole portfolio.
The expected loss is also above the \$12.88M of booked upfront premium: on that premium alone, the
portfolio would lose money in an average trial. [Step 5](https://reinsurance.dev/applications/portfolio-analytics/#step-5-risk-versus-opportunity) adds the
reinstatement premium, which reverses that.

:::caution[Watch Out]
With only 20 trials, [Value at Risk (VaR)](https://reinsurance.dev/glossary/#var-value-at-risk) at 90% is the second-worst trial and TVaR at 90% averages the worst two: a tail estimated from two trials, which one simulated storm could change, as flagged in the [standalone pricing application](https://reinsurance.dev/applications/standalone-pricing/#how-the-answer-moves-with-the-assumptions).
The roll-up method is exact at any trial count; the numbers here are as trustworthy as 20
trials allow. A production portfolio's roll-up runs over [10,000 to 1,000,000+ trials](https://reinsurance.dev/toolkit/trials/#demo-scale), which smooths
that noise out across many more tail trials. The effect appears on a smaller scale in the demo data's [1,000-trial tier](https://reinsurance.dev/toolkit/trials/#demo-scale).
:::

Python (`python/caldera_re/portfolio_rollup.py`, runnable on the web page):

```python
"""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 roll-up and the metrics take any selection of contracts, which is all
[a slice of the portfolio](https://reinsurance.dev/applications/portfolio-analytics/#step-4-slice-the-portfolio) requires.

## Step 4: slice the portfolio

A single tail number is a summary; the portfolio manager's next question is where the risk
is concentrated. A slice is a narrower selection. Include or
exclude whole contracts by business unit, line of business, or binding status, at the same
participations, and roll up again. And because the portfolio [year event loss table (YELT)](https://reinsurance.dev/glossary/#yelt-year-event-loss-table) preserves occurrence detail,
a slice can also be cut within contracts by peril or region. Each subportfolio produced this way
has its own metrics and its own risk-versus-opportunity assessment, which is how reinsurers
[actually manage](https://reinsurance.dev/financial-modelling/portfolios/#a-portfolio-is-a-bundle-of-bundles): business units and
lines of business are steered individually, each against its own objectives as well as the
enterprise's.

The simplest slice is expected loss along the contracts' tags. EL is additive, so it can be sliced
along any tag (region, peril, cedent), and the slices always sum to the portfolio's \$14.59M:

*[Interactive figure: Portfolio concentration chart. See the web page: https://reinsurance.dev/applications/portfolio-analytics/]*

By region, the portfolio's EL is roughly half US, a third European windstorm, a fifth Japanese quake; by peril,
earthquake is the single largest line at 43%, spread across three cedents and two continents. A portfolio that looks diversified by cedent can still be
concentrated by peril: Caldera Re's earthquake exposure spans SunCoast, Pacific Mutual, and Atlas,
so a change to earthquake pricing or retrocession affects three placements at once. Slicing the same EL two ways shows that concentration.

Each slice is a subportfolio, so it is rolled up and measured on its own. The code below cuts the portfolio by region and by peril and reads EL, TVaR, and capital
per subportfolio. Slice ELs sum to the enterprise EL
along any axis, but slice TVaRs do not: by region they sum to \$30.92M compared with the
enterprise's \$23.51M, because measuring slices separately includes diversification within each
slice but excludes diversification between slices.

Python (`python/caldera_re/portfolio_slices.py`, runnable on the web page):

```python
"""Slicing Caldera Re's portfolio: subportfolios as narrower selections.

A slice is not a new operation. It is the same pipeline -- select
constituents, roll up, measure -- run on a narrower selection: include or
exclude whole contracts by a tag (here region and peril; in production also
business unit, line of business, binding status) at the same participations.
Each subportfolio gets its own metrics, because business units, lines of
business, and capital sources are managed individually.

Two lessons fall out of the numbers:

    1. Expected loss is additive -- the slice ELs sum to the enterprise EL
       exactly, along any axis.
    2. Tail risk is not -- each slice keeps the diversification *within*
       itself but loses the diversification *across* slices, so the slice
       TVaRs sum to more than the enterprise TVaR (but less than the
       standalone sum).
"""

from caldera_re.portfolio_rollup import ALPHA, CONTRACTS, metrics, roll_up
from caldera_re.report import heading, table

def slice_portfolio(r: dict, axis: str) -> dict:
    """Roll up one subportfolio per value of a contract tag.

    Returns {tag value: {'members', 'el', 'tvar', 'k'}}, ordered by EL.
    """
    buckets = {}
    for c in CONTRACTS:
        buckets.setdefault(CONTRACTS[c][axis], []).append(c)
    slices = {}
    for key, members in buckets.items():
        sub = sum(r['participated'][m] for m in members)  # the slice's roll-up
        m = metrics(sub)
        slices[key] = {'members': members, 'el': m['el'], 'tvar': m['tvar'],
                       'k': m['tvar'] - m['el']}
    return dict(sorted(slices.items(), key=lambda kv: -kv[1]['el']))

if __name__ == '__main__':
    r = roll_up()
    enterprise = metrics(r['portfolio'])

    for axis in ('region', 'peril'):
        heading(f"Subportfolios by {axis} "
                f"(each its own roll-up + metrics, $M)")
        el_sum = tvar_sum = 0.0
        rows = []
        for key, s in slice_portfolio(r, axis).items():
            el_sum += s['el']; tvar_sum += s['tvar']
            rows.append((key, '+'.join(s['members']), s['el'], s['tvar'],
                         s['k']))
        rows.append(('Sum', None, el_sum, tvar_sum, None))
        rows.append(('Whole', None, enterprise['el'], enterprise['tvar'],
                     None))
        table(['Slice', 'Contracts', 'EL', f"TVaR({ALPHA:.0%})", 'K'], rows,
              fmt=['{}', '{}', '{:.2f}', '{:.2f}', '{:.2f}'])
        print(f"Slice ELs sum to the enterprise EL exactly; slice TVaRs "
              f"({tvar_sum:.2f}) exceed the enterprise TVaR "
              f"({enterprise['tvar']:.2f})\nbecause the diversification "
              f"*across* slices is lost when they are measured apart.\n")
```

:::note[Consider]
Expected loss can be sliced exactly because it is a mean, but **TVaR** cannot be sliced the same way. Hurricane and windstorm losses dominate the
portfolio's worst trials, so US and EU exposure contribute more to the
tail than their EL share suggests, and Japan less. Attributing the portfolio's TVaR back to its
contracts (a fair split of the diversified tail, not the additive EL) is a harder problem
([contribution to TVaR (Co-TVaR)](https://reinsurance.dev/glossary/#co-tvar-contribution-to-tvar) / Euler allocation, derived for one contract on the [perspectives](https://reinsurance.dev/financial-modelling/perspectives/#what-the-split-gives-you) page), and more advanced portfolio analytics requires it.
EL, which is additive, is sliced here; the tail attribution is its own subject.
:::

EL and capital, for the whole portfolio or any slice of it, are the risk side of
[the comparison with premium](https://reinsurance.dev/applications/portfolio-analytics/#step-5-risk-versus-opportunity).

## Step 5: risk versus opportunity

The portfolio manager can weigh the portfolio's risk against what it earns. Three of the
six contracts have paid reinstatements, so what the portfolio earns depends on whether their
reinstatement premium is counted, and so does the capital: the reinstatement premium is paid in
the loss trials, and [capital is measured on the outcome net of it](https://reinsurance.dev/applications/standalone-pricing/#step-4-reinstatement-premium-in-the-price).

| | Without reinstatement premium | With reinstatement premium |
| --- | ---: | ---: |
| Booked upfront premium | \$12.88M | \$12.88M |
| Expected reinstatement premium | | \$3.24M |
| [Loss ratio](https://reinsurance.dev/glossary/#loss-ratio) (EL / premium income) | 113.3% | 90.5% |
| Premium income − EL − expenses | −\$2.44M | \$0.80M |
| Required capital $K$ | \$8.92M | \$7.25M |
| [Risk-adjusted return on capital (RAROC)](https://reinsurance.dev/glossary/#raroc-risk-adjusted-return-on-capital) | −27.4% | **11.0%** |

Without reinstatement premium the booked upfront premium does not cover the expected loss: the premiums
of C1, C4 and C6 were set on the understanding that their cedents pay again for restored limit,
and the view without reinstatement premium omits the payment. Portfolio decisions use the view with reinstatement premium.

One caveat from the
[standalone pricing framework](https://reinsurance.dev/applications/standalone-pricing/#step-3-from-metrics-to-premium) applies here too:
the booked premium is treated as fully earned revenue (no brokerage, ceding commission, or
premium taxes are netted out), so these ratios share that framework's illustrative scope. Their values also depend on the demo portfolio's
[arithmetic-convenience premiums](https://reinsurance.dev/caldera-re/#why-this-portfolio), which are far above
real placements' rates.

Whether the portfolio clears the profitability hurdle depends on its [risk-adjusted return on capital (RAROC)](https://reinsurance.dev/glossary/#raroc-risk-adjusted-return-on-capital):

$$
\begin{aligned}
\text{RAROC} &= \frac{\text{premium} + \text{reinstatement premium} - \text{EL} - \epsilon\,\text{EL}}{K} \\
&= \frac{12.88 + 3.24 - 14.594 - 0.730}{7.25} \approx 11.0\%
\end{aligned}
$$

With a [cost of capital](https://reinsurance.dev/glossary/#cost-of-capital) of 10%, a portfolio returning **11.0%**
on its required capital clears the profitability hurdle, by 1.0 point. The margin is due to the capital basis,
and the counterfactual shows it. Capitalised one by one, the same six contracts require \$18.95M
between them, net of their reinstatement premium. Premium income, expected loss, and expenses are
unchanged, so the numerator is the same \$0.80M:

$$
\text{RAROC}^{\text{standalone}} = \frac{12.88 + 3.24 - 14.594 - 0.730}{18.95} \approx 4.2\%
$$

The same premium income earns 11.0% on the portfolio's capital and **4.2%** on the sum of standalone capital.
The contracts' worst trials overlap
little (US hurricane beside Japanese earthquake and European windstorm), so held together they
require less capital than apart. The portfolio clears a profitability hurdle that the same contracts,
capitalised separately, do not.

The [standalone return](https://reinsurance.dev/applications/standalone-pricing/#step-6-the-verdict) on SunCoast's
program, 9.0%, is a different calculation and is not the counterfactual here: it is computed on three
contracts at 100%, \$32M of premium on \$47.18M of program capital, where the portfolio holds six
contracts at Caldera Re's participations. It is below the profitability hurdle for the same reason. A deal judged
alone has its own capital held against its whole tail and cannot use capital already held for
other business.

Both returns are computed from a 20-trial tail (the 11.0% depends on the same two-trial TVaR
flagged in the Watch Out in Step 3). The difference between them comes from the capital basis; the
returns themselves are estimates, and a real portfolio manager
would compute them from a production-scale roll-up before basing decisions on them.

Python (`python/caldera_re/portfolio_analytics.py`, runnable on the web page):

```python
"""Portfolio analytics on Caldera Re's rolled-up book of business.

The roll-up (`portfolio_rollup.py`) reduces the portfolio to loss
distributions; this module asks the business questions of them: what capital
does the portfolio tie up, is the premium adequate for the risk, and where is
the risk concentrated. Pricing reuses the standalone-pricing conventions --
capital K = TVaR_alpha - EL of the outcome net of reinstatement premium,
RAROC = (premium + expected reinstatement premium - EL - expenses) / K -- so the
portfolio verdict is directly comparable to the per-program one.

The headline is the capital basis. With reinstatement premium counted, the
same margin earns 11.0% on the portfolio's diversified capital but only 4.2%
on the sum of the contracts' standalone capital -- the portfolio clears the
10% hurdle only because it is diversified.
"""

import numpy as np

from caldera_re.portfolio_rollup import ALPHA, CONTRACTS, roll_up
from caldera_re.reinstatement_premium import reinstatement_factor
from caldera_re.reinstatement_pricing import capital as outcome_capital
from caldera_re.report import fields, heading, table

R_C = 0.10      # cost of capital rate
EPSILON = 0.05  # expense load as a fraction of EL

# 100% layer premiums from the Caldera Re Bible (0C); Caldera Re books its
# participation share of each.
PREMIUM_100 = {'C1': 14.0, 'C2': 5.0, 'C3': 13.0,
               'C4': 6.0, 'C5': 17.0, 'C6': 13.0}

# C7, proposed (not in the six-contract portfolio): premium at 100%.
PROPOSED_C7 = 6.0

# Reinstatement clauses (Caldera Re Bible): occurrence limit, number of
# reinstatements, and the premium rate of each, all pro rata to amount. These
# repeat the terms passed to catxol() in portfolio_rollup.contract_gross();
# change them together.
REINSTATEMENTS = {'C1': (30.0, 1, 1.0), 'C4': (10.0, 1, 1.0),
                  'C6': (30.0, 2, (1.0, 0.5))}

def reinstatement_income(gross: dict) -> dict:
    """Each contract's reinstatement premium per trial at 100%, on its premium
    (zero for a contract without a paid reinstatement)."""
    out = {}
    for c, g in gross.items():
        if c in REINSTATEMENTS:
            limit, n, rate = REINSTATEMENTS[c]
            out[c] = PREMIUM_100[c] * reinstatement_factor(g, limit, n, rate)
        else:
            out[c] = np.zeros_like(g)
    return out

def view(r: dict, with_reinstatement: bool) -> dict:
    """The portfolio's economics at its booked premium, with or without the
    reinstatement premium its contracts earn. Capital is measured on the
    outcome: participated gross, less reinstatement premium when counted."""
    rip = reinstatement_income(r['gross'])
    if not with_reinstatement:
        rip = {c: 0 * v for c, v in rip.items()}
    part = {c: CONTRACTS[c]['participation'] for c in CONTRACTS}
    outcome = {c: part[c] * (r['gross'][c] - rip[c]) for c in CONTRACTS}
    total = sum(outcome.values())
    booked = premium_book()['total']
    income = booked + sum(part[c] * rip[c].mean() for c in CONTRACTS)
    el = r['port']['el']
    margin = income - (1 + EPSILON) * el
    k = outcome_capital(total, ALPHA)
    k_sum = sum(outcome_capital(x, ALPHA) for x in outcome.values())
    return {'premium': booked, 'reinstatement_premium': income - booked,
            'income': income, 'loss_ratio': el / income, 'margin': margin,
            'k': k, 'k_sum': k_sum, 'raroc': margin / k,
            'raroc_standalone': margin / k_sum, 'outcome': total}

def premium_book() -> dict:
    """Caldera Re's booked premium for the participated portfolio."""
    booked = {c: PREMIUM_100[c] * CONTRACTS[c]['participation']
              for c in CONTRACTS}
    return {'by_contract': booked, 'total': sum(booked.values())}

def capital(r: dict) -> dict:
    """Capital K = TVaR - EL, per standalone contract and for the portfolio."""
    standalone = {c: s['tvar'] - s['el'] for c, s in r['standalone'].items()}
    return {
        'standalone': standalone,
        'sum_standalone': sum(standalone.values()),
        'portfolio': r['port']['tvar'] - r['port']['el'],
    }

def concentration(r: dict, axis: str) -> dict:
    """Participated EL by slice -- additive, so slices sum to portfolio EL."""
    buckets = {}
    for c in CONTRACTS:
        key = CONTRACTS[c][axis]
        buckets[key] = buckets.get(key, 0.0) + r['standalone'][c]['el']
    return dict(sorted(buckets.items(), key=lambda kv: -kv[1]))

if __name__ == '__main__':
    r = roll_up()
    prem = premium_book()
    p = r['port']

    heading("Booked premium, reinstatement premium and loss ratio, participated ($M)")
    rip = reinstatement_income(r['gross'])
    rows = []
    for c in CONTRACTS:
        s = r['standalone'][c]
        booked = prem['by_contract'][c]
        booked_rip = CONTRACTS[c]['participation'] * rip[c].mean()
        rows.append((c, CONTRACTS[c]['participation'], s['el'], booked,
                     booked_rip, s['el'] / (booked + booked_rip)))
    table(['C', 'part', 'pEL', 'prem', 'reinst. prem', 'LR'], rows,
          fmt=['{}', '{:.1%}', '{:.2f}', '{:.2f}', '{:.2f}', '{:.1%}'])

    without, with_ = view(r, False), view(r, True)
    print()
    heading(f"The portfolio at its booked premium (K = TVaR({ALPHA:.0%}) - EL of the outcome)")
    table(['', 'Without', 'With reinstatement premium'], [
        ('Booked premium', without['premium'], with_['premium']),
        ('Expected reinstatement premium', without['reinstatement_premium'],
         with_['reinstatement_premium']),
        ('Expected loss', p['el'], p['el']),
        ('Margin after expenses', without['margin'], with_['margin']),
        ('Portfolio K', without['k'], with_['k']),
        ('Sum of standalone K', without['k_sum'], with_['k_sum']),
    ], fmt=['{}', '{:.2f}M', '{:.2f}M'])
    table(['RAROC', 'Without', 'With reinstatement premium'], [
        ('on portfolio K', without['raroc'], with_['raroc']),
        ('on sum of standalone K', without['raroc_standalone'],
         with_['raroc_standalone']),
    ], fmt=['{}', '{:.1%}', '{:.1%}'])
    print(f"\nWith reinstatement premium the portfolio clears the {R_C:.0%} cost of")
    print("capital on its diversified capital and not on the sum of its contracts'")
    print("standalone capital. Without it, the booked premium does not cover the")
    print("expected loss.")

    print()
    heading("Concentration: participated EL by slice (adds to portfolio EL)")
    for axis in ('region', 'peril', 'cedent'):
        buckets = concentration(r, axis)
        print("  " + axis + ": " + ", ".join(
            f"{key} {v:.2f} ({v/p['el']:.0%})" for key, v in buckets.items()))
```

:::note[From the Field]
A reinsurer once ran two underwriting teams that each priced their own line to standalone profitability targets and quietly
rejected anything that fell short of it. Both teams were individually disciplined and the reinsurer was
forgoing profitable business: each was declining risk that was underpriced alone but
accretive to the portfolio, because neither team could see the other's diversification. The fix was not
better standalone models; it was setting the profitability targets on the portfolio, where the marginal contract is
judged against the portfolio it joins, not an empty portfolio. That is marginal pricing, and portfolio analytics is its prerequisite: you cannot
price at the margin until you can measure the portfolio. The same argument makes the broader
case for efficient portfolio management: a live, enterprise-level portfolio that every
business unit prices and steers against, rather than each team managing by its own slice.
:::

The \$7.25M of capital in that 11.0% is measured on the diversified portfolio, net of reinstatement
premium. [The diversification](https://reinsurance.dev/applications/portfolio-analytics/#the-diversification-carried-implicitly) is derived below on the loss
alone, without reinstatement premium, and then compared on both capital bases.

## The diversification, carried implicitly

The contrast at the end of Step 5 (contracts that fall short of the profitability hurdle capitalised one by one, a
portfolio that clears it) has one cause. The
[standalone pricing application](https://reinsurance.dev/applications/standalone-pricing/#step-5-the-program-view-who-gets-the-diversification)
reported diversification within SunCoast's program (\$32.72M of capital saved because the three layers' losses
do not peak in the same trials) and claimed that the same effect operates between programs, across the
whole portfolio. The portfolio numbers in Step 3 already contain it. The
[Portfolios](https://reinsurance.dev/financial-modelling/portfolios/#expected-loss-adds-tail-risk-diversifies) page
demonstrates the mechanism on these six contracts, each measured standalone on its own
participated losses:[^basis] the standalone TVaRs sum to \$36.54M against the portfolio's
\$23.51M, a \$13.02M,[^rounding] 35.6% [diversification benefit](https://reinsurance.dev/glossary/#diversification-benefit),
because the contracts' worst losses are in different trials. That page does not compute the capital
consequence. In capital, the same comparison is:

[^basis]: Everything in this comparison is at Caldera Re's participations; the standalone pricing
    application priced C1–C3 at the 100% layer, so its figures are larger than the ones here.

[^rounding]: The tail difference and both capital lines below are computed from the unrounded
    values and displayed to two decimals; with the displayed inputs the tail difference comes to
    13.03 and the second capital line to 21.95.
    The rounding rule is stated in the [Caldera Re appendix](https://reinsurance.dev/caldera-re/#portfolio).

$$
\begin{aligned}
K^{\text{portfolio}} &= \text{TVaR} - \text{EL} &&= 23.51 - 14.59 &&= 8.92 \\
\textstyle\sum_i K_i &= \textstyle\sum_i \text{TVaR}_i - \textstyle\sum_i \text{EL}_i &&= 36.54 - 14.59 &&= 21.94
\end{aligned}
$$

The EL term is the same \$14.59M in both lines, because
[expected loss adds exactly](https://reinsurance.dev/financial-modelling/portfolios/#expected-loss-adds-tail-risk-diversifies),
so the capital difference equals the tail difference: $21.94 - 8.92 = 13.02$. Put as a
multiplier, $21.94 / 8.92 = 2.46$: held as one diversified portfolio, the same contracts require
$1/2.46$ of the capital they would standalone, a **capital efficiency** of **2.46×**
([glossary](https://reinsurance.dev/glossary/#capital-efficiency)). The \$13.02M difference is capital Caldera Re does
not have to hold; the \$21.94M is a counterfactual, six separately capitalised positions, not a
sum Caldera Re ever held.

Both capital figures above are measured on the loss alone, without reinstatement premium. The
return in Step 5 uses capital measured on the outcome net of reinstatement premium, which is lower
for the portfolio and for the sum of standalone positions:

| Capital measured on | Portfolio | Sum of standalone | Capital efficiency |
| --- | ---: | ---: | ---: |
| Loss, without reinstatement premium | \$8.92M | \$21.94M | 2.46× |
| Outcome net of reinstatement premium (Step 5) | \$7.25M | \$18.95M | 2.61× |

Run deal by deal, this comparison has little use: a reinsurer already holding a portfolio prices
against that portfolio, not against an empty one, so the standalone figure is an upper bound rather
than the number to act on.[^standalone-max] Adding up standalone TVaRs is exactly
the mistake the roll-up exists to avoid, and once a contract is written, its standalone
performance is close to irrelevant. The portfolio manager tracks the portfolio figures: premium income,
expected loss, capital at risk, return on capital, and each contract's contribution to
them. The diversification is not a step in the pipeline; it is implicit in the portfolio view. It is the reason the portfolio and standalone results differ, and the reason marginal pricing *(planned)* prices new deals against the portfolio.

:::tip[Key Insight]
Diversification lowers the capital, never the expected loss. Caldera Re still expects
\$14.59M of claims whether the contracts are on one portfolio or are split across six
reinsurers; combining them changes the tail and the capital required for it.
This is the mechanical reason a reinsurer values an uncorrelated risk more highly than a
correlated one of the same expected loss: it adds less to the portfolio's tail, requires less
capital, and has a lower technical premium. In Caldera Re's premium arithmetic (Step 5), the 11.0% return is on the diversified \$7.25M of capital, not the
\$18.95M the same contracts would require standalone.
:::

The capital figure is only as reliable as the sum used to compute it, and
[that sum requires every contract's losses aligned to the same trials](https://reinsurance.dev/applications/portfolio-analytics/#at-production-scale).

## At production scale

[At production scale](https://reinsurance.dev/applications/production-scale/) discusses how rows and trials, currency,
and reproducibility change for the roll-up, together with the partition-by-trial shape it shares
with the other pipelines. The roll-up adds one requirement of its own.

**Alignment.** Every contract's losses must be aligned to the same trials before they are
summed: the roll-up is a sum within a trial, so a mismatched trial index silently corrupts
the tail. At occurrence resolution the same requirement applies to the full occurrence key,
`(trial_id, timestamp, event_id)`: the alignment is finer, and the key determines how the data
is laid out on disk and whether a roll-up queries it or scans it. Where every contract's rows are
co-partitioned by trial, one pass over a trial's partition that sums them is faster than a query per
contract for its trial loss. Shared trial identity
across every cedent's YELT (the [demo data's fixed trial ids](https://reinsurance.dev/toolkit/trials/)) makes
the sum meaningful. In production this is a join key you validate at every boundary, the way
[risk profiling validates a loss set at ingest](https://reinsurance.dev/applications/risk-profiling/#step-1-validate-and-reconcile-the-loss-sets).
Validation catches a trial id outside 1..N; it cannot catch a dropped trial, an index permuted
within 1..N, or loss sets from different simulations. A `trial_id` identifies a trial within one simulation and its trial numbering, so two vendors' YELTs
with 10,000 trials each pass a trial-count check, match on every `trial_id`, and still pair
unrelated simulated futures: trial 17 in one has no relation to trial 17 in the other, and the
sum treats the two loss sets as independent whatever the actual dependence between the risks.
The precondition is a property of how the loss sets were produced (one simulation, one set of
trials), not of their row counts or ids. A reinsurer checks it against each loss set's provenance:
the model, version and run that produced it, which it
[tracks for every loss set](https://reinsurance.dev/applications/production-scale/#loss-set-multiplicity).
Combining loss sets from different vendors, model versions or climate views needs a dependence
treatment that this site does not cover yet.

| Roll-up resolution | Join key | What a mismatch corrupts |
| --- | --- | --- |
| Trial (produces a YLT) | `trial_id` | The AEP tail: a shifted index adds losses from different trials as if they were one |
| Occurrence (produces a YELT) | `(trial_id, timestamp, event_id)` | The OEP tail and every occurrence term downstream: one event booked twice, or against the wrong trial |
| Either, on loss sets from different simulations | Both keys match as integers but name unrelated trials (and, across catalogs, unrelated events) | Every tail metric and the capital on it: the sum pairs unrelated futures, which, for independently generated simulations, imposes independence between the loss sets; for simulations that share a catalog or random streams it imposes an arbitrary coupling. No check on the ids detects it. Expected loss still adds correctly |

:::note[Remember]
The portfolio is not a report you generate at quarter-end; it is a live object whose YELT you keep
rolled up so that the next question (what does one more contract do to it?) is cheap to answer. If you can roll up a portfolio's YELT in milliseconds, you can price the portfolio at the margin in milliseconds. Build the roll-up to be
fast and pure, and marginal pricing is the same function called twice.
:::

The same trial identity is the partition key in
[the computational shape all three pipelines share](https://reinsurance.dev/applications/production-scale/#one-computational-shape).

## What carries forward

Portfolio analytics is one pipeline: **select the constituents, roll up
(participation, then sum), measure, slice, and weigh risk against opportunity**. The roll-up
reduces the portfolio's losses to a single distribution; the analytics compute from it the numbers
on which Caldera Re bases its portfolio decisions. For Caldera Re those numbers are a \$14.59M expected loss, an \$8.92M
capital requirement on the loss, a **capital efficiency of 2.46×** (the same contracts would need
\$21.94M measured standalone), and, with the reinstatement premium received on its contracts, an 11.0%
return on required capital, above the 10% profitability hurdle by 1.0 point. The demo-scale calculation produces those figures; the same pipeline applies at
production trial counts.

A **[retrocession](https://reinsurance.dev/glossary/#retrocession)** contract can use the portfolio YELT as its subject loss, so Caldera Re can buy protection on its own
portfolio, or free the capital held against it to write more business. Either way, a retro layer attaches to the loss the roll-up produces. **Marginal pricing** *(planned)* also uses the rolled-up portfolio YELT: the value of one
new contract is what it adds to the portfolio's capital, its [Marginal TVaR](https://reinsurance.dev/glossary/#marginal-tvar) less its expected loss, computed as the roll-up run twice, with and without the contract, and
differenced. Standalone pricing asked whether a deal clears alone; marginal pricing asks whether it
clears here, against this portfolio. At 10,000 to
1,000,000+ trials, portfolio analytics uses
[a partitioned computation over named, versioned loss sets](https://reinsurance.dev/applications/production-scale/).

[^exposure-mgmt]: Lloyd's, ["Exposure management"](https://www.lloyds.com/market-resources/capital-planning-group/exposure-management), which makes the function responsible for "the Corporation's understanding and control of catastrophe risk, at the individual Syndicate and Market levels". The Lloyd's Market Association (LMA), ["Exposure Management"](https://lmalloyds.com/specialist-areas/exposure-management/), whose team "supports managing agents in enhancing their approach to catastrophe risk modelling, exposure data quality and regulatory preparedness". Both describe the function at market level, at the Corporation and at the association; that a reinsurer's own equivalent carries the same name, and which job title holds it, is this site's assessment.

[^standalone-max]: Rodney E. Kreps, ["Investment-Equivalent Reinsurance Pricing"](https://www.casact.org/sites/default/files/2021-02/pubs_proceed_proceed98_980101.pdf), *Proceedings of the Casualty Actuarial Society*, 1998. Because "most reinsurers already have a book of business and hence contracts mutually support each other", a stand-alone risk load "can be regarded as a reasonable maximum": pricing a deal alone "is equivalent to assuming that the whole book is fully correlated" and "will result in larger risk loads than are actually needed".
