Skip to content
Property-cat reinsurance analytics for software engineers

Financial Modelling · Section 6

Financial perspectives

Subject, covered, gross, retained, and net: the same trial losses seen from each side of a contract, and which metrics decompose exactly across them.

We have defined the financial terms and composed contracts from them, and every one of those contracts does the same thing: it turns a stream of subject losses into a gross loss. But that gross loss is only half of what a contract produces: the same transformation also fixes what the cedent retains. Foundations named these loss perspectives in business terms; here we attach the canonical notation and ask which of them we can actually compute with a single contract in hand.

PerspectiveNotationDefinition
SubjectLjsubjectL_j^{\text{subject}}The contract’s input: everything the cedent’s book presents in trial jj, before any term applies
CoveredLjcovL_j^{\text{cov}}The covered, in-period slice left after the contract’s filter and contract period
GrossLjgross=C(Ljsubject)L_j^{\text{gross}} = C(L_j^{\text{subject}})What the reinsurer assumes after the full composition CC
Cedent retainedLjret=Ljsubject−LjgrossL_j^{\text{ret}} = L_j^{\text{subject}} - L_j^{\text{gross}}The part of the subject loss the contract did not absorb: what the cedent keeps

Subject and covered differ whenever the filter is not the identity, and conflating them mislabels real dollars. On Trial 9, SunCoast presents a $272.1M subject, and Contract 1’s Florida-hurricane filter (SunCoast’s $30M xs $10M Florida-hurricane catastrophe excess of loss (CatXoL) contract) keeps $216.8M of it, the covered loss. Neither quantity is new: CC is the composition the dissection built up, its first two terms are the filter and the period, and the covered loss is their output, the point in the pipeline two terms short of the gross. Subtracting the gross from the subject gives the cedent’s retained loss, and the coverage boundary splits it in two:

Ljsubject=(Ljsubject−Ljcov)⏟never covered+(Ljcov−Ljgross)⏟covered, kept+Ljgross⏟cededL_j^{\text{subject}} = \underbrace{\left(L_j^{\text{subject}} - L_j^{\text{cov}}\right)}_{\text{never covered}} + \underbrace{\left(L_j^{\text{cov}} - L_j^{\text{gross}}\right)}_{\text{covered, kept}} + \underbrace{L_j^{\text{gross}}}_{\text{ceded}}

Every trial’s subject loss splits, dollar for dollar, into three parts: what the contract never covered, what it covered but did not absorb, and what the reinsurer took. The first two together are the cedent’s retained loss LjretL_j^{\text{ret}}. The never-covered part carries no information about the contract: the filter and period drop it by construction, whatever the amounts. What the transforming terms (the occurrence layer and the aggregate limit) did is recorded entirely in how the covered loss divides between kept and ceded. That division is measured on the covered loss LcovL^{\text{cov}} (for Contract 1, SunCoast’s Florida-hurricane loss) and its kept part, written Lret,cov=Lcov−LgrossL^{\text{ret,cov}} = L^{\text{cov}} - L^{\text{gross}}. The split is what shows what a contract does: how much of the risk it covers moved.

Because the perspectives divide the same dollars, a metric on one constrains the others, but how it constrains them depends on the metric.

Contract 1 ($30M xs $10M, 1 reinstatement, Jan 1 to Dec 31) across the 20 trials: each trial's subject loss as what the reinsurer took (ceded), what the contract covered but did not absorb (kept), and what the filter and the period never covered. Outlined bars mark the covered stream's 4 worst trials at α = 80%: trials 2, 6, 9 and 19. Hover a bar for its parts. The sliders are shared by the three perspective charts on this page.

Expected loss splits exactly. Expected loss (EL) is an average, and averaging is linear, so the covered stream’s expected loss is precisely the sum of the ceded and kept expected losses:

EL(Lcov)=EL(Lgross)+EL(Lret,cov)\text{EL}(L^{\text{cov}}) = \text{EL}(L^{\text{gross}}) + \text{EL}(L^{\text{ret,cov}})

For Contract 1 across SunCoast’s 20-trial demo tier, that is 43.9M=17.4M+26.5M43.9\text{M} = 17.4\text{M} + 26.5\text{M}: the layer absorbs about 40% of SunCoast’s expected covered (Florida-hurricane) loss, and SunCoast keeps the rest, along with everything the filter never covered.

Subject → Never covered: $231.7MSubject → Covered: $43.9MCovered → Kept: $26.5MCovered → Ceded: $17.4MSubject: $275.6MSubject $275.6MNever covered: $231.7MNever covered $231.7MCovered: $43.9MCovered $43.9MKept: $26.5MKept $26.5MCeded: $17.4MCeded $17.4M

Expected loss under Contract 1 ($30M xs $10M, 1 reinstatement, Jan 1 to Dec 31): SunCoast's $275.6M subject splits into $231.7M never covered and $43.9M covered, and the covered loss into $17.4M ceded and $26.5M kept. Every flow adds up because the mean is linear. The sliders are shared by the three perspective charts on this page.

Tail risk does not.

The two tails are different sets of trials, and that is the whole argument. At α=80%\alpha = 80\% the covered stream’s four worst trials are 2, 6, 9, and 19; the gross stream’s are 3, 6, 9, and 19. Trial 2 is a heavy Florida-hurricane year in which the layer paid $30M, and Trial 3 a lighter one in which it paid $34.6M across more storms. The gross ranks them the other way round from the covered loss.

1234567891011121314151617181920Covered (FL HU)Covered (FL HU) — Trial 1: 1.4MCovered (FL HU) — Trial 2: 122.6M (in the tail)Covered (FL HU) — Trial 3: 55.7MCovered (FL HU) — Trial 4: 17.0MCovered (FL HU) — Trial 5: 33.9MCovered (FL HU) — Trial 6: 61.5M (in the tail)Covered (FL HU) — Trial 7: 37.4MCovered (FL HU) — Trial 8: 0.0MCovered (FL HU) — Trial 9: 216.8M (in the tail)Covered (FL HU) — Trial 10: 7.1MCovered (FL HU) — Trial 11: 38.7MCovered (FL HU) — Trial 12: 52.7MCovered (FL HU) — Trial 13: 28.5MCovered (FL HU) — Trial 14: 21.0MCovered (FL HU) — Trial 15: 24.7MCovered (FL HU) — Trial 16: 29.8MCovered (FL HU) — Trial 17: 0.0MCovered (FL HU) — Trial 18: 1.1MCovered (FL HU) — Trial 19: 127.8M (in the tail)Covered (FL HU) — Trial 20: 0.0MGross (Contract 1)Gross (Contract 1) — Trial 1: 0.0MGross (Contract 1) — Trial 2: 30.0MGross (Contract 1) — Trial 3: 34.6M (in the tail)Gross (Contract 1) — Trial 4: 3.9MGross (Contract 1) — Trial 5: 11.3MGross (Contract 1) — Trial 6: 35.7M (in the tail)Gross (Contract 1) — Trial 7: 10.1MGross (Contract 1) — Trial 8: 0.0MGross (Contract 1) — Trial 9: 60.0M (in the tail)Gross (Contract 1) — Trial 10: 0.0MGross (Contract 1) — Trial 11: 27.9MGross (Contract 1) — Trial 12: 30.0MGross (Contract 1) — Trial 13: 18.5MGross (Contract 1) — Trial 14: 0.0MGross (Contract 1) — Trial 15: 6.0MGross (Contract 1) — Trial 16: 19.7MGross (Contract 1) — Trial 17: 0.0MGross (Contract 1) — Trial 18: 0.0MGross (Contract 1) — Trial 19: 60.0M (in the tail)Gross (Contract 1) — Trial 20: 0.0M
Contract 1 across the 20 demo-tier trials: one cell per trial for the covered stream (top) and the gross stream (bottom), with each stream's own worst four trials at α = 80% filled. The filled sets differ in one cell (trial 2 against trial 3), and that one cell is the difference between the wrong subtraction and the exact split; hover a cell for the trial's loss.

The correct decomposition fixes one tail (the covered stream’s worst kk trials) and adds the ceded and kept losses within it. Conditioning on a single fixed set makes expectation linear again, so the split is exact. This is a Co-TVaR (Euler) allocation,1 the co-measure that portfolio capital attribution is built on, a subject the portfolio analytics story marks out and does not yet work through:

TVaRα(Lcov)=Co-TVaRαcov(Lgross)+Co-TVaRαcov(Lret,cov)\text{TVaR}_\alpha(L^{\text{cov}}) = \text{Co-TVaR}_\alpha^{\text{cov}}(L^{\text{gross}}) + \text{Co-TVaR}_\alpha^{\text{cov}}(L^{\text{ret,cov}})

where Co-TVaRαcov(⋅)\text{Co-TVaR}_\alpha^{\text{cov}}(\cdot) averages its argument over the trials in the covered stream’s tail. The cedent’s residual tail risk on the covered stream, conditional on its own worst trials, is TVaRα(Lcov)−Co-TVaRαcov(Lgross)\text{TVaR}_\alpha(L^{\text{cov}}) - \text{Co-TVaR}_\alpha^{\text{cov}}(L^{\text{gross}}), exact, with nothing left over.

Subject, covered tail → Never covered, covered tail: $178.7MSubject, covered tail → Covered: $132.2MCovered → Kept: $85.8MCovered → Ceded: $46.4MSubject, covered tail: $310.9MSubject, covered tail $310.9MNever covered, covered tail: $178.7MNever covered, covered tail $178.7MCovered: $132.2MCovered $132.2MKept: $85.8MKept $85.8MCeded: $46.4MCeded $46.4M
On the covered tailOwn tail
Covered$132.2M$132.2M
Ceded$46.4M$47.6M
Kept$85.8M$86.1M
Ceded + kept$132.2M$133.7M
Covered tail: trials 2, 6, 9 and 19. Ceded tail: 3, 6, 9 and 19. Kept tail: 2, 7, 9 and 19.

Tail risk under Contract 1 ($30M xs $10M, 1 reinstatement, Jan 1 to Dec 31). Every figure is the stream's average over the covered stream's 4 worst trials at α = 80% (trials 2, 6, 9 and 19), a co-measure, so the flows add up: the covered stream's TVaR of $132.2M is $46.4M ceded plus $85.8M kept; the subject and never-covered figures are the same trials' averages, not those streams' own tails. The table sets each stream's own TVaR, on its own worst trials, beside it. The ceded stream's own worst trials are 3, 6, 9 and 19, and subtracting its TVaR, $47.6M, from the covered TVaR gives $84.6M against the exact $85.8M. The sliders are shared by the three perspective charts on this page.

A metrics store that keeps per-perspective summaries (EL, Value at Risk (VaR), and TVaR per contract per perspective) cannot produce the residual tail at all, because the co-measure needs the trial-aligned vectors of both streams: which trials are in the covered tail, and what the gross was in those trials. The covered and gross columns have to survive together, keyed by trial, to the point of measurement; summarise either one first and the split is gone.

At the demo tier’s N=20N = 20, the α=90%\alpha = 90\% tail is k=2k = 2 trials, which is why the subtraction happens to work there and fails one step lower. Production loss sets run 10,000 to 1,000,000+ trials, where the two tails routinely diverge; the 1,000-trial tier shows the same mechanism at a resolution closer to production.

A different but equally valid question is “how bad can the kept loss get on its own?” That is TVaRα(Lret,cov)\text{TVaR}_\alpha(L^{\text{ret,cov}}): the metrics from the toolkit applied to the kept distribution, whose tail is its own worst trials. For Contract 1 at 90%90\% that is 124.7M124.7\text{M}, larger than the 112.3M112.3\text{M} kept slice of the covered tail, because the kept loss peaks in different trials than the covered loss does. Each number has a job. The Co-TVaR slice attributes an agreed total: it says how much of the covered tail the contract removed and how much it left, and the parts reconcile exactly. It is the right frame for judging the contract’s tail relief, and the frame portfolio capital allocation builds on. The standalone figure sizes the retained position itself: the cedent’s balance sheet suffers the kept loss’s own worst trials whether or not the covered total was extreme in them, so capital and risk appetite for the retained loss are set from TVaRα(Lret,cov)\text{TVaR}_\alpha(L^{\text{ret,cov}}). Attribution takes the conditional number; holding the residual takes the standalone one.

The generator below reproduces every figure on this page (the per-trial covered split, the exact EL decomposition, and the Co-TVaR identity at both 90%90\% and 80%80\%):

caldera_re/perspectives.py Python
"""Derived loss perspectives for Contract 1 (SunCoast FL HU CatXoL).

Subject is everything the cedent presents; the contract's filter and contract
period select the COVERED loss, and the remaining terms produce the gross.
The interesting split is of the covered stream, trial by trial:

    L_kept[s] = L_covered[s] - L_gross[s]

This is the reconciliation source for the "Financial perspectives" section of
the Financial modelling page. It shows two things:

  * EL splits EXACTLY (expectation is linear):
        EL(covered) = EL(gross) + EL(kept)

  * TVaR does NOT subtract. TVaR(covered) - TVaR(gross) averages over two
    DIFFERENT tail sets (each perspective's own worst trials). The cedent's
    residual tail risk is a Co-TVaR (Euler) allocation conditioned on the
    covered tail:
        TVaR(covered) = Co-TVaR_cov(gross) + Co-TVaR_cov(kept)

Run with:
    cd python && PYTHONPATH=. uv run --with numpy --with pandas --with pyyaml \
        python3 caldera_re/perspectives.py
"""

import numpy as np

from caldera_re.catxol import catxol
from caldera_re.data import cedents
from caldera_re.loss_filter import loss_filter
from caldera_re.contract_period import contract_period
from caldera_re.report import fields, table
from caldera_re.utils import ep_index

PERIOD = ('2024-01-01', '2024-12-31')

N = 20
A, L, REINST = 10.0, 30.0, 1
CRIT = dict(peril='HU', geography_id='FL')


def trial_array(result, column, n=N):
    """Collapse a contract's YELT output to a length-n per-trial array
    indexed by trial_id (1..n), filling absent trials with 0."""
    arr = np.zeros(n)
    for tid, value in result.groupby('trial_id')[column].sum().items():
        arr[int(tid) - 1] = value
    return arr


TIE_DECIMALS = 9


def tail_indices(values, alpha):
    """Indices of the worst k trials of `values`, k = ep_index(N, alpha).
    Matches the site's VaR/TVaR convention (mean of the worst k).

    Two trials whose values agree to TIE_DECIMALS are a tie, and a tie goes
    to the later trial (stable ascending sort, last k taken). The rounding
    is the tie rule: a layer that pays its limit in two trials can produce
    30.0 in one and 29.999999999999996 in the other, and without it the
    JavaScript twin (src/lib/perspectives.js) could pick a different trial.
    """
    k = ep_index(len(values), alpha)
    return np.argsort(np.round(values, TIE_DECIMALS), kind='stable')[len(values) - k:]


if __name__ == '__main__':
    # Covered = the contract's covered, in-period loss per trial: the subject
    # (everything SunCoast presents) after the filter and contract period.
    covered_rows = contract_period(loss_filter(cedents.suncoast, **CRIT), *PERIOD)
    cov_by_trial = covered_rows.groupby('trial_id')['loss'].sum()
    covered = np.zeros(N)
    for tid, v in cov_by_trial.items():
        covered[int(tid) - 1] = v

    # Gross = the contract output. Standalone gross is reported at 100%
    # (participation applied at roll-up).
    result = catxol(cedents.suncoast, attachment=A, limit=L,
                    reinstatements=REINST, **CRIT)
    gross = trial_array(result, 'gross_loss')

    # Kept = covered - gross, trial by trial: the covered loss the cedent
    # keeps. (The cedent also keeps everything the filter never covered.)
    kept = covered - gross

    print("Contract 1 - per-trial perspectives (FL HU, 30M xs 10M, 1 reinst.)")
    table(['Trial', 'Covered', 'Gross', 'Kept'],
          [(s + 1, covered[s], gross[s], kept[s])
           for s in range(N) if covered[s] > 0],
          fmt=['{}', '{:.1f}M', '{:.1f}M', '{:.1f}M'])

    # --- EL splits exactly (EL is linear) ---
    el_covered, el_gross, el_kept = covered.mean(), gross.mean(), kept.mean()
    print(f"\nExpected loss (mean over {N} trials):")
    fields([
        ('EL(covered)', el_covered),
        ('EL(gross)', el_gross, f"{el_gross / el_covered:.0%} of the covered "
                                f"loss ceded in expectation"),
        ('EL(kept)', el_kept),
    ], indent=2, sep=' = ')
    print(f"  check: EL(gross) + EL(kept) = {el_gross + el_kept:.1f}M "
          f"= EL(covered)  -> exact (EL is linear)")

    # --- TVaR does NOT subtract ---
    for alpha in (0.90, 0.80):
        k = ep_index(N, alpha)
        cov_tail = tail_indices(covered, alpha)
        gross_tail = tail_indices(gross, alpha)

        tvar_covered = covered[cov_tail].mean()
        tvar_gross = gross[gross_tail].mean()
        tvar_kept = kept[tail_indices(kept, alpha)].mean()

        # Co-TVaR conditioned on the COVERED tail (the correct additive split).
        co_gross = gross[cov_tail].mean()
        co_kept = kept[cov_tail].mean()

        print(f"\nTail risk at alpha = {alpha:.0%}  (k = {k} worst trials):")
        print(f"  covered tail = trials {sorted((cov_tail + 1).tolist())}")
        print(f"  gross   tail = trials {sorted((gross_tail + 1).tolist())}"
              f"{'  (same set)' if set(cov_tail) == set(gross_tail) else '  (DIFFERENT set)'}")
        fields([
            ('standalone TVaR(covered)', tvar_covered),
            ('standalone TVaR(gross)', tvar_gross),
            ('standalone TVaR(kept)', tvar_kept, "kept's OWN worst trials"),
        ], indent=2, sep=' = ')
        print(f"  WRONG  TVaR(covered) - TVaR(gross) = "
              f"{tvar_covered - tvar_gross:6.1f}M")
        print(f"  Co-TVaR split on the covered tail (exact):")
        fields([('Co-TVaR_cov(gross)', co_gross),
                ('Co-TVaR_cov(kept)', co_kept)], indent=4, sep=' = ')
        print(f"    check {co_gross:.1f} + {co_kept:.1f} = {co_gross + co_kept:.1f}M"
              f" = TVaR(covered)  -> exact")
        print(f"  => cedent residual tail risk (in the covered stream's worst trials)")
        print(f"     = TVaR(covered) - Co-TVaR_cov(gross) = "
              f"{tvar_covered - co_gross:.1f}M = Co-TVaR_cov(kept)")

We have dissected one CatXoL, formalised the building blocks, recomposed the canonical contract types, and seen how a single contract partitions risk. The final step is to combine many contracts: the portfolio roll-up shows where diversification emerges, and the portfolio analytics story reads it as a business position.

  1. Dirk Tasche, “Capital Allocation to Business Units and Sub-Portfolios: the Euler Principle”, arXiv:0708.2542, 2008. Gives the Euler contribution to expected shortfall as the average of the component over the outcomes in which the portfolio total is in its own tail, an elementary conditional expectation whose contributions sum to the portfolio figure. Rodney Kreps, “Riskiness Leverage Models”, Proceedings of the Casualty Actuarial Society, 2005, names such additive components co-measures and derives TVaR’s as “the average value of the variable of interest in the situations where the total is greater than” the quantile. Gary G. Venter, “A Survey of Capital Allocation Methods with Commentary”, ASTIN Colloquium Berlin, 2003, Appendix 3, gives the name this site uses and the continuous form of the definition, “the mean loss for the ith unit in the case where total losses are over the qth quantile”; John A. Major and Stephen J. Mildenhall, Introduction to Capital Modeling and Portfolio Management, CAS Monograph 15, Casualty Actuarial Society, 2026, §3.3, gives the Euler gradient of TVaR and notes it is “often called the coTVaR”. Tasche derives the general form under smoothness conditions that imply a density; the rank-based version used here fixes the tail set instead, so the components add exactly on a discrete trial set. ↩