Skip to content
Property-cat reinsurance analytics for software engineers

Financial Modelling · Section 2

Dissecting a contract

Watch a CatXoL transform a single trial's losses one financial term at a time (filter, contract period, occurrence layer, and aggregate limit), then participation at roll-up.

Financial modelling turns a cedent’s subject loss into the reinsurer’s gross loss by applying a contract’s terms. The fastest way to understand the mechanism is to watch it happen: take one contract, one trial, and apply each term in turn.

Financial modelling is the application of financial terms, and a contract is where those terms are written down. A reinsurance program is a stack of contracts, and each contract specifies how it reshapes the losses it sees: which perils it covers, when it attaches, how much it pays, and where it stops.

Each financial term is a transformation on trial loss data: it takes a year event loss table (YELT), or the output of a preceding term, as input and produces a modified set of losses as output. Applying a contract means composing its terms into a pipeline that transforms the cedent’s subject losses into the reinsurer’s gross loss.

Foundations introduced the common contract types: quota share, catastrophe excess of loss (CatXoL), and aggregate excess of loss (AggXoL, also called stop loss). Each comes in variants and carries optional features (reinstatements, event limits, franchise deductibles), each defined in the glossary. But underneath the taxonomy, contracts are far less varied than their names suggest:

The same few operations keep reappearing across contract types. Contracts as compositions tabulates which term each type uses, generated from the catalog itself. Caldera Re’s Contract 1 composes those terms into a working CatXoL, dissected here one financial term at a time; the term catalog formalises each term.

One structural property governs how financial modelling is reasoned about and computed: contract terms are applied to each trial independently. Nothing in a contract reaches across trials: an aggregate limit sums occurrences within one trial, participation scales that trial’s loss, a filter keeps that trial’s covered events. Trial 7 never references Trial 8.1 This independence lets us decompose the calculation to the level of a single trial, where each term’s effect is transparent, and it means the whole distribution is the same four-term evaluation fanned out over every trial and collected:

No trial’s evaluation waits on another’s, so the trials can run in any order or all at once.

Applying Contract 1’s terms step by step to one trial, Trial 9 of SunCoast’s YELT, shows how the losses change at each stage.

Contract 1 is the CatXoL in SunCoast Insurance’s reinsurance program. Caldera Re takes a 14.5% participation in the layer, sharing it with other reinsurers. Its terms:

TermValue
Covered lossFlorida hurricane (peril = HU, geography = FL)
Contract period2024-01-01 to 2024-12-31
Occurrence layer$30M limit excess of $10M attachment
Reinstatements1
Participation (Caldera Re’s share)14.5%

In contract language: “$30M excess of $10M each and every Florida hurricane, with one reinstatement, for the 2024 year, at 14.5%.”2 Each clause of that wording is one financial term, and the steps apply those terms to Trial 9 in composition order.

Trial 9 is one simulated year of SunCoast’s book, 30 occurrences across two perils: earthquakes in California and Arizona, and hurricanes along the Gulf and Atlantic coasts.

Earthquake Hurricane

Trial 9 subject losses: 30 occurrences, $272.1M total, one bar per occurrence at its event date (height is the loss amount, colour the peril). Two large Florida hurricanes ($102.4M in September and $80.8M in October) dominate a year otherwise made of small earthquakes and non-Florida storms.

The full subject total is $272.1M. Contract 1 will not see most of it: it covers only Florida hurricane, and only a slice of that. Each term can only narrow what reaches the reinsurer.

PerilOccurrencesSubject loss
Earthquake (CA / AZ)22$27.6M
Hurricane (all geographies)8$244.5M
 of which Florida hurricane4$216.8M
Total30$272.1M

The contract covers Florida hurricane. The first term is a filter: keep only occurrences whose peril is HU and geography is FL; drop everything else.

Earthquake Hurricane

Step 1: filter to Florida hurricane. Four occurrences survive ($29.7M, $102.4M, $4.0M, $80.8M); the other 26 are dimmed out. FL HU subject total: $216.8M.

Four occurrences survive, a $216.8M Florida hurricane subject total.3 Note that the colour encodes peril alone, so the filter also has to drop the Texas and Louisiana hurricanes (same colour, wrong geography). We formalise the filter term in the catalog.

Surviving occurrenceSubject loss
Jul 12, FL hurricane$29.7M
Sep 6, FL hurricane$102.4M
Sep 6, FL hurricane$4.0M
Oct 5, FL hurricane$80.8M
Total$216.8M

The contract period term keeps only events within the inception-to-expiry window (here, 2024-01-01 to 2024-12-31). All four Florida hurricanes fall inside it, so for Trial 9 the set of surviving occurrences is unchanged. In a different year (a mid-year inception, or a storm in January 2025), events outside the window would be dropped at this step. The contract period catalog page formalises it.

Contract periodOccurrencesSubject
Within 2024-01-01 – 2024-12-314 of 4$216.8M

These are the terms that shape the payout. $30M excess of $10M, each and every occurrence: for every Florida hurricane the contract ignores the first $10M (the attachment) and pays the next $30M (the limit). Anything above $40M returns to the cedent’s retention. Formally, the occurrence gross for each event is:

Loccgross=min⁡(max⁡(Lsubject−10,  0),  30)L^{\text{gross}}_{\text{occ}} = \min(\max(L^{\text{subject}} - 10,\; 0),\; 30)

The contract pays the part of each occurrence that falls inside the layer; what lies below the attachment ($10M) and above the top of the layer ($40M) stays with the cedent as its retained loss. The occurrence excess catalog page formalises this term.

Hurricane

Step 3: occurrence layer, $30M xs $10M. The $102.4M and $80.8M storms exceed the limit and cap at $30M each; the $29.7M storm pays $19.7M; the $4.0M storm does not reach the $10M attachment and pays nothing.

  • $102.4M and $80.8M both exceed the $30M limit → each caps at $30M
  • $29.7M falls inside the layer → pays $29.7M − $10M = $19.7M
  • $4.0M does not reach the $10M attachment → pays nothing

The occurrence layer is applied to each event independently; the per-occurrence results sum to $79.7M.

OccurrenceSubjectOccurrence gross
Jul 12$29.7M$19.7M
Sep 6$102.4M$30.0M
Sep 6$4.0M$0.0M
Oct 5$80.8M$30.0M
Total$216.8M$79.7M

The number of reinstatements is how many times the occurrence layer’s limit can be restored once it is exhausted, so a layer with nn reinstatements can pay its limit n+1n + 1 times in total: the original limit, plus one restoration per reinstatement.4 Mathematically, that is an aggregate limit of (n+1)×ℓ(n + 1) \times \ell, a claims-side encoding that sets the reinstatement premium mechanics aside (the CatXoL page spells out what it drops). With one reinstatement, that is (1+1)×30M=60M(1 + 1) \times 30\text{M} = 60\text{M}. Sum the per-occurrence payouts in event order and cap the running total at $60M.

Hurricane

Step 4: aggregate limit, $60M. Bars are the per-occurrence gross from step 3. The cumulative line climbs to $49.7M after the July and September storms, then the October storm would push it to $79.7M, but the aggregate limit clips it at $60M.

The cumulative line climbs to $49.7M after the July storm and the two September storms (one of which did not reach the occurrence attachment and contributes nothing). The October hurricane’s $30M payout would push the total to $79.7M, but the aggregate limit clips it at $60M. The contract has exhausted its cover; the $19.7M of the final event that exceeds the remaining aggregate capacity is retained by SunCoast. The aggregate limit binds. The aggregate excess catalog page formalises the term, including how the aggregate limit erodes occurrence by occurrence.

OccurrenceOccurrence grossCumulativeAfter $60M cap
Jul 12$19.7M$19.7M$19.7M
Sep 6$30.0M$49.7M$49.7M
Sep 6$0.0M$49.7M$49.7M
Oct 5$30.0M$79.7M$60.0M
Trial gross$60.0M

Caldera Re does not hold the entire layer: it takes a 14.5% participation, sharing the contract with other reinsurers. Participation is not one of the contract’s terms (the composition ends at the aggregate limit, defining the contract at 100%) and enters as a scaling at portfolio roll-up: multiply the gross loss by the reinsurer’s share. For Trial 9, the $60M standalone layer gross becomes Caldera Re’s booked share of 0.145×60M=8.7M0.145 \times 60\text{M} = 8.7\text{M}.

Trial 9Standalone (100% layer)Caldera Re’s share (14.5%)
Gross loss$60.0M$8.7M

The $60M figure (the full layer, before participation) is what appears for Contract 1’s standalone metrics and in the pipeline distribution below; Caldera Re’s $8.7M booked share enters only once the contract joins the portfolio.

Four financial terms, applied in sequence, turn Trial 9’s $272.1M of subject loss into a $60M standalone gross loss, of which Caldera Re books its 14.5% share, $8.7M, at roll-up:

The runnable version evaluates the four contract terms on Trial 9, applies the roll-up participation, and reproduces the numbers above:

caldera_re/contract1_trial9.py Python
"""Single-trial dissection of Contract 1 (SunCoast FL HU CatXoL, 30M xs 10M,
1 reinstatement) on Trial 9 of the Caldera Re SunCoast YELT.

This is the reconciliation source for the single-trial walkthrough on the
Financial modelling page. It prints, for Trial 9:

  * the full subject loss timeline (every occurrence, all perils)
  * each financial term applied in sequence and its effect
  * the per-occurrence and aggregate arrays the page's timeline charts use

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

import pandas as pd

from caldera_re.data import cedents
from caldera_re.report import table

S = 9                       # the trial we dissect
A, L = 10.0, 30.0           # attachment, occurrence limit ($M)
REINSTATEMENTS = 1
AGG_LIMIT = L * (1 + REINSTATEMENTS)   # 60M
PARTICIPATION = 0.145   # Caldera Re's share of the layer, applied at roll-up


def occurrences(rows):
    """Collapse YELT rows to one row per occurrence (event), summing the loss
    across geographies and lines of business. Each event has a single peril."""
    return (rows.groupby(['timestamp', 'event_id', 'peril'], as_index=False)
            .agg(loss=('loss', 'sum'),
                 geos=('geography_id', lambda g: '/'.join(sorted(set(g))))))


if __name__ == '__main__':
    sc = cedents.suncoast
    s9 = sc[sc['trial_id'] == S].copy()

    # --- Subject: every occurrence in the trial, all perils ---
    subj = occurrences(s9).sort_values('timestamp').reset_index(drop=True)
    print(f"Trial {S}: {len(s9)} YELT rows -> {len(subj)} occurrences "
          f"({(subj.peril == 'HU').sum()} HU, {(subj.peril == 'EQ').sum()} EQ)\n")

    print("Subject loss timeline (all occurrences):")
    table(['date', 'event', 'peril', 'geo', 'loss'],
          subj[['timestamp', 'event_id', 'peril', 'geos', 'loss']],
          fmt=['{}', '{}', '{}', '{}', '{:.2f}M'])
    print(f"\n  Subject total (all perils): {subj['loss'].sum():.1f}M")

    # --- Step 1: Filter YELT rows to covered peril + geography (FL HU) ---
    covered_rows = s9[(s9['peril'] == 'HU')
                      & (s9['geography_id'] == 'FL')]
    covered = occurrences(covered_rows).sort_values('timestamp').reset_index(drop=True)
    print(f"\nStep 1 - Filter (peril=HU, geography=FL): "
          f"{len(subj)} -> {len(covered)} occurrences")
    print(f"  FL HU subject total: {covered['loss'].sum():.1f}M")
    print(f"  losses: {[round(x, 2) for x in covered['loss']]}")

    # --- Step 2: Contract period (2024 full year) ---
    ts = pd.to_datetime(covered['timestamp'])
    rows_before_period = len(covered)
    covered = covered[(ts >= '2024-01-01') & (ts <= '2024-12-31')].copy()
    print(f"\nStep 2 - Contract period (2024-01-01 .. 2024-12-31): "
          f"{rows_before_period} -> {len(covered)} (no change for T{S})")

    # --- Step 3: Occurrence excess (30M xs 10M), per occurrence ---
    covered['gross_occ'] = (covered['loss'] - A).clip(lower=0).clip(upper=L)
    print(f"\nStep 3 - Occurrence excess ({L:.0f}M xs {A:.0f}M):")
    table(['date', 'subject', 'gross'],
          covered[['timestamp', 'loss', 'gross_occ']],
          fmt=['{}', '{:.2f}M', '{:.2f}M'])
    print(f"  per-occurrence gross total: {covered['gross_occ'].sum():.2f}M")
    print(f"  gross array: {[round(x, 2) for x in covered['gross_occ']]}")

    # --- Step 4: Aggregate cap (60M = (1 + 1) x 30M) ---
    covered['cumulative'] = covered['gross_occ'].cumsum()
    covered['cumulative_capped'] = covered['cumulative'].clip(upper=AGG_LIMIT)
    agg = covered['gross_occ'].sum()
    capped = min(agg, AGG_LIMIT)
    print(f"\nStep 4 - Aggregate cap ({AGG_LIMIT:.0f}M, {REINSTATEMENTS} reinst.):")
    table(['date', 'gross', 'cum', 'cum capped'],
          covered[['timestamp', 'gross_occ', 'cumulative',
                   'cumulative_capped']],
          fmt=['{}', '{:.2f}M', '{:.2f}M', '{:.2f}M'])
    print(f"  aggregate gross {agg:.2f}M -> capped {capped:.2f}M "
          f"({'binds' if capped < agg else 'no bind'})")

    # --- Step 5: Participation ---
    # Standalone metrics are reported at 100% (the full layer); Caldera Re's
    # participation share is applied at portfolio roll-up.
    booked = capped * PARTICIPATION
    print(f"\nStep 5 - Participation ({PARTICIPATION:.1%}):")
    print(f"  standalone gross (100% layer): {capped:.2f}M")
    print(f"  Caldera Re booked share:        {booked:.2f}M")

    print(f"\n=> Trial {S} Contract 1 standalone gross: {capped:.1f}M "
          f"(Caldera Re books {booked:.1f}M at {PARTICIPATION:.1%})")

Trial 9 was one trial. Run the same sequence of terms across all 20 trials of Caldera Re’s SunCoast demo tier, collect the standalone gross losses, and sort them, and you have Contract 1’s loss distribution (reported at 100%, before participation). Only the input losses differ between trials; the terms are the same, so every stage of the pipeline has its own EP curve, the loss distribution as of that term. Caldera Re’s 14.5% participation scales the whole distribution down proportionally at roll-up.

Contract 1 pipeline across all 20 trials, reported at 100% (before participation). Trial 9 (the trial we dissected) and Trial 19 are where the aggregate limit binds: toggle 'Standalone gross' to see it clip both to $60M. Click the legend pills to toggle curves; hover to emphasise one.

The dissection gave us the intuition: a contract is a pipeline of financial terms applied per trial. Next, Contracts as compositions formalises each term and shows how each contract type in the catalog is built from the same blocks.

  1. Aman Bahl, Oliver Baltzer, Andrew Rau-Chaplin, and Blesson Varghese, “Parallel Simulations for Analysing Portfolios of Catastrophic Event Risk”, Workshop at SC12, 2012 (co-authored by this site’s author). The aggregate analysis stage is “executed for each Layer and each trial in the YET”, and every implementation the paper benchmarks runs a single thread per trial, each performing “aggregate analysis for a single trial”. ↩ ↩2

  2. United Property and Casualty Insurance Company, “Property Catastrophe Excess of Loss Reinsurance Agreement”, SEC EDGAR exhibit 10.4, 2012, whose first layer is written in this shape: liability “in respect of each and every Loss Occurrence for the Ultimate Net Loss over and above an initial Ultimate Net Loss of $15,000,000 each and every Loss Occurrence, subject to a limit of liability to the Reinsurer of $10,000,000 each and every Loss Occurrence”. Guy Carpenter, Glossary of Reinsurance Terms, records “each and every” as “an expression used in treaties and certificates”. The wording names the loss occurrence where this site names the covered peril. ↩

  3. Every figure on the site is computed from the unrounded losses in the demo CSVs and displayed to one decimal, so displayed components can miss a displayed total by a unit in the last place. Here the four occurrences show as $29.7M, $102.4M, $4.0M, and $80.8M: $216.9M as displayed, $216.8M unrounded. The convention is stated in the Caldera Re appendix. ↩

  4. IRMI, “More Porridge Please: Reinstatements in Reinsurance”, Expert Commentary. A reinstatement restores an exhausted limit for the remainder of the period; a layer with one reinstatement can pay its limit twice. ↩