Skip to content
Property-cat reinsurance analytics for software engineers

Financial Modelling · Section 3

Contracts as compositions

The algebra of financial terms. How quota share, CatXoL, and AggXoL are each a composition of the same small set of building blocks.

The dissection translated Contract 1 into four terms applied in sequence, with participation booked at roll-up. Those same terms (with different parameters, and different blocks in different combinations) assemble every other Caldera Re contract too. Each is a catastrophe excess of loss (CatXoL), an aggregate excess of loss (AggXoL), or a quota share:

  • Contract 5 (Baltica EU Windstorm Quota Share, 25%): filter to EU windstorm, check the contract period, scale every loss by 25%.
  • Contract 3 (SunCoast All-US AggXoL, $50M xs $300M, net of C1+C2): filter to SunCoast’s US perils, subtract C1 and C2 gross losses (inuring), check the contract period, sum all occurrences per trial and apply an attachment and limit to the total.

Notice the reuse: a CatXoL applies an attachment and limit per occurrence, and an AggXoL applies the same operation to the aggregate; a quota share’s event limit is the CatXoL’s occurrence excess with zero attachment. The same five blocks recur (plus, in Contract 3, inuring, which is a relationship between contracts rather than a block). The parameters are the predicate PP, the window (t0,t1)(t_0, t_1), the attachment AA, the limit ℓ\ell, the number of reinstatements nn, and the scaling factor ff:

Building blockContract type
Quota ShareCatXoLAggXoL
Loss filter✓ (peril / geography)✓ (peril / geography)✓ (peril / geography)
Contract period✓✓✓
Scaling✓ (cession %)——
Occurrence excessoptional (event limit)✓ (AA, ℓ\ell)—
Aggregate excessoptional (annual limit)✓ (00, (1+n)⋅ℓ(1{+}n) \cdot \ell)✓ (AA, ℓ\ell)

Each block is formalised in the term catalog, where you will find its formula, its effect on Trial 9 in isolation, a runnable implementation over the year event loss table (YELT), and the exceedance probability (EP) curves it produces across all 20 trials of the demo tier:

TermWhat it doesSignature
Loss filterKeeps the rows for which PP is true and drops the rest; no loss value changesfilter(P)\text{filter}(P)
Contract periodDrops occurrences whose timestamp falls outside inception-to-expiry (a losses-occurring basis)period(t0,t1)\text{period}(t_0, t_1)
ScalingMultiplies every loss by a constant: a quota share's cession inside a composition, the participation share at roll-up; the distribution keeps its shape and every metric scales linearlyscale(f)\text{scale}(f)
Occurrence excessPays min⁡(max⁡(L−A,0),ℓ)\min(\max(L - A, 0), \ell) on each occurrence independentlyoccexcess(A,ℓ)\text{occ}_{\text{excess}}(A, \ell)
Aggregate excessApplies the attachment and limit to the trial's running total, booking each occurrence's erosion of the layer as its output rowaggexcess(A,ℓ)\text{agg}_{\text{excess}}(A, \ell)

All five obey the YELT in, YELT out principle: no block collapses the YELT to trial level. The filter, the contract period, and scaling act on each row. The occurrence excess acts on the occurrence total and splits its gross across the occurrence’s rows in proportion to their losses, so its output keeps geography and line of business. The aggregate excess satisfies the principle by tracking the layer’s erosion occurrence by occurrence and booking each occurrence’s contribution to the aggregate gross as its output row, so terms composed after it still receive rows at the resolution it was given. The aggregate excess page works through the mechanics. Optional terms such as triggers are defined in the glossary; the catalog does not cover them.

The five blocks are the within-contract vocabulary. Inuring (Contract 3’s subtraction of C1 and C2) is a relationship between two contracts’ outputs, not a sixth block. It takes one contract’s gross loss out of another’s subject loss before that contract’s own terms run, so C3’s subject is SunCoast’s loss net of C1 and C2. The Programs page covers it.

A contract is a composition of terms: each contract type composes the same terms, differing only in which ones it uses and how they are parameterised.

We write a composition as C=Tk∘⋯∘T1C = T_k \circ \cdots \circ T_1: apply T1T_1 first, then T2T_2 to the result, and so on. Data flows left to right (we call the flow a pipeline, but that is not its formal term), while the formula reads right to left, following the mathematical convention for function composition. Every contract below begins, on the right, with a filter and a contract period selecting the losses it sees.

The three contract types are three compositions over the same five terms. Each leaves some terms out, and each gives the terms it keeps its own parameters:

In code the composition is a fold over the terms. Each term is a function from a YELT to a YELT, so compose takes them in the order the formula is written (rightmost applied first) and returns a function of the same shape, which is what lets a composed contract be composed again. Run on SunCoast’s YELT, Contract 1 written this way reproduces the dissection’s $60M on Trial 9 and the $17.39M expected loss the standalone pricing story prices C1 on:

caldera_re/compose.py Python
from functools import partial, reduce

import pandas as pd

from caldera_re.aggregate_excess import aggregate_excess
from caldera_re.contract_period import contract_period
from caldera_re.data import cedents
from caldera_re.loss_filter import loss_filter
from caldera_re.occurrence_excess import occurrence_excess


def compose(*terms):
    """Compose financial terms the way the formula is written.

    compose(T_k, ..., T_2, T_1)(yelt) == T_k(...T_2(T_1(yelt))): the argument
    list reads right to left, as C = T_k o ... o T_1 does, and the data flows
    left to right through the terms. Each term is a function YELT -> YELT, so
    the result is itself a function YELT -> YELT and can be composed again.
    """
    def contract(yelt: pd.DataFrame) -> pd.DataFrame:
        return reduce(lambda acc, term: term(acc), reversed(terms), yelt)
    return contract


if __name__ == '__main__':
    # Contract 1, written as its formula reads:
    #   CatXoL(10, 30, 1) = agg_excess(0, 60) o occ_excess(10, 30)
    #                       o period(2024-01-01, 2024-12-31) o filter(HU, FL)
    # occurrence_excess writes 'gross_loss'; the aggregate cap chains on it.
    contract_1 = compose(
        partial(aggregate_excess, attachment=0, limit=60, loss_column='gross_loss'),
        partial(occurrence_excess, attachment=10, limit=30),
        partial(contract_period, period_start='2024-01-01', period_end='2024-12-31'),
        partial(loss_filter, peril='HU', geography_id='FL'),
    )

    result = contract_1(cedents.suncoast)
    by_trial = result.groupby('trial_id')['gross_loss'].sum()

    N = 20
    gross = by_trial.reindex(range(1, N + 1), fill_value=0.0)
    print("Contract 1 as a composition of four terms (100% layer)")
    print(f"  Trial 9 gross:  {gross[9]:.1f}M")
    print(f"  Gross EL:       {gross.mean():.2f}M")
    print()
    print("Per-trial standalone gross, all 20 trials:")
    for trial_id, g in gross.items():
        print(f"  trial {trial_id:2d}: {g:5.1f}M")

Every composition defines its contract at 100%: the whole layer, however the placement is split among reinsurers. Participation is deliberately absent from the definitions: it is a scaling applied at portfolio roll-up, where Caldera Re books its share of each contract. Standalone contract metrics are reported at 100% for the same reason: the full-layer result isolates a contract’s own behaviour from how its placement happens to be split. An implementer who writes participation into the contract and then scales again at roll-up double-counts it. Both rules are indexed on Scope, conventions & assumptions.

The three signatures are rendered from each contract page’s own catalog entry, so they cannot drift from it:

ContractSignature
Quota shareQS(q)=scale(q)∘period(t0,t1)∘filter(P)\text{QS}(q) = \text{scale}(q) \circ \text{period}(t_0, t_1) \circ \text{filter}(P)
Catastrophe excess of lossCatXoL(A,ℓ,n)=aggexcess(0,  (1+n)⋅ℓ)∘occexcess(A,ℓ)∘period(t0,t1)∘filter(P)\text{CatXoL}(A, \ell, n) = \text{agg}_{\text{excess}}(0,\; (1+n) \cdot \ell) \circ \text{occ}_{\text{excess}}(A, \ell) \circ \text{period}(t_0, t_1) \circ \text{filter}(P)
Aggregate excess of lossAggXoL(A,ℓ)=aggexcess(A,ℓ)∘period(t0,t1)∘filter(P)\text{AggXoL}(A, \ell) = \text{agg}_{\text{excess}}(A, \ell) \circ \text{period}(t_0, t_1) \circ \text{filter}(P)

Instantiated with Caldera Re’s parameters (attachments and limits in millions of dollars, each composition applied to its own cedent’s YELT, so no filter names the cedent):

The simplest structure: a fixed cession percentage qq behind a filter and a contract period. Contract 5 cedes a quarter of Baltica’s European windstorm losses:

QS(0.25)=scale(0.25)∘period(2024-01-01,2024-12-31)∘filter(peril=WS)\text{QS}(0.25) = \text{scale}(0.25) \circ \text{period}(\text{2024-01-01}, \text{2024-12-31}) \circ \text{filter}(\text{peril} = \text{WS})

Optional event and annual limits insert an occurrence excess and an aggregate excess before the scaling.

The occurrence excess clips each event; the aggregate excess caps the annual total (the limit (1+n)⋅ℓ(1+n)\cdot\ell encodes nn reinstatements). Contract 1 is $30M excess of $10M per Florida hurricane, with one reinstatement:

CatXoL(10,30,1)=aggexcess(0,60)∘occexcess(10,30)∘period(2024-01-01,2024-12-31)∘filter(peril=HU∧geography=FL)\text{CatXoL}(10, 30, 1) = \text{agg}_{\text{excess}}(0, 60) \circ \text{occ}_{\text{excess}}(10, 30) \circ \text{period}(\text{2024-01-01}, \text{2024-12-31}) \circ \text{filter}(\text{peril} = \text{HU} \wedge \text{geography} = \text{FL})

Drops the per-occurrence layer: the aggregate excess applies directly to the trial total. Contract 3 is $50M excess of $300M on SunCoast’s whole US book, net of C1 and C2 (the netting is the inuring relationship, applied to the subject before the composition runs):

AggXoL(300,50)=aggexcess(300,50)∘period(2024-01-01,2024-12-31)∘filter(peril∈{HU,EQ})\text{AggXoL}(300, 50) = \text{agg}_{\text{excess}}(300, 50) \circ \text{period}(\text{2024-01-01}, \text{2024-12-31}) \circ \text{filter}(\text{peril} \in \{\text{HU}, \text{EQ}\})

The term catalog and contract catalog formalise each block and each contract in turn. Beyond the canonical types, one contract can also forward its output to another. The programs page covers the forwarding and inuring relationships that turn a stack of contracts into a graph. And every contract produces not only a gross loss but a full set of financial perspectives: subject, covered, gross, retained, and net.