# 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.

Source: https://reinsurance.dev/financial-modelling/composition/

The [dissection](https://reinsurance.dev/financial-modelling/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)](https://reinsurance.dev/glossary/#catxol-catastrophe-excess-of-loss), an [aggregate excess of loss (AggXoL)](https://reinsurance.dev/glossary/#aggxol-aggregate-excess-of-loss), 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](https://reinsurance.dev/financial-modelling/programs/#inuring), which is a relationship between contracts rather than a block). The parameters are the predicate $P$, the window $(t_0, t_1)$, the attachment $A$, the limit $\ell$, the number of reinstatements $n$, and the scaling factor $f$:

<table>
  <thead>
    <tr>
      <th rowspan="2">Building block</th>
      <th colspan="3">Contract type</th>
    </tr>
    <tr>
      <th>Quota Share</th>
      <th>CatXoL</th>
      <th>AggXoL</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Loss filter</td>
      <td>✓ (peril / geography)</td>
      <td>✓ (peril / geography)</td>
      <td>✓ (peril / geography)</td>
    </tr>
    <tr>
      <td>Contract period</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Scaling</td>
      <td>✓ (cession %)</td>
      <td>—</td>
      <td>—</td>
    </tr>
    <tr>
      <td>Occurrence excess</td>
      <td>optional (event limit)</td>
      <td>✓ ($A$, $\ell$)</td>
      <td>—</td>
    </tr>
    <tr>
      <td>Aggregate excess</td>
      <td>optional (annual limit)</td>
      <td>✓ ($0$, $(1{+}n) \cdot \ell$)</td>
      <td>✓ ($A$, $\ell$)</td>
    </tr>
  </tbody>
</table>

## The five building blocks

Each block is formalised in the **[term catalog](https://reinsurance.dev/financial-modelling/terms/)**, where you will find its formula, its effect on Trial 9 in isolation, a runnable implementation over the [year event loss table (YELT)](https://reinsurance.dev/glossary/#yelt-year-event-loss-table), and the [exceedance probability (EP) curves](https://reinsurance.dev/glossary/#ep-curve-exceedance-probability-curve) it produces across all 20 trials of the [demo tier](https://reinsurance.dev/toolkit/trials/#demo-scale):

*[Table: Catalog table. See the web page: https://reinsurance.dev/financial-modelling/composition/]*

All five obey the [YELT in, YELT out](https://reinsurance.dev/toolkit/trials/#the-yelt) principle: no block collapses the YELT to trial level. The filter, the contract period, and scaling act on each row. The [occurrence excess](https://reinsurance.dev/financial-modelling/terms/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](https://reinsurance.dev/glossary/#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](https://reinsurance.dev/financial-modelling/terms/aggregate-excess/) works through the mechanics. Optional terms such as [triggers](https://reinsurance.dev/glossary/#trigger) are defined in the glossary; the catalog does not cover them.

The five blocks are the within-contract vocabulary. [Inuring](https://reinsurance.dev/glossary/#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](https://reinsurance.dev/financial-modelling/programs/#inuring) page covers it.

## Composing contracts from the blocks

A contract is a [composition](https://reinsurance.dev/glossary/#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 = T_k \circ \cdots \circ T_1$: apply $T_1$ first, then $T_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](https://reinsurance.dev/financial-modelling/terms/filter/) and a [contract period](https://reinsurance.dev/financial-modelling/terms/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:

```mermaid
graph LR
    F["filter(P)"]:::Contract --> T["period(t₀, t₁)"]:::Contract
    T --> O["occ_excess(A, ℓ)<br/>QS: event limit or —<br/>CatXoL: (A, ℓ)<br/>AggXoL: —"]:::Process
    O --> G["agg_excess(A, ℓ)<br/>QS: annual limit or —<br/>CatXoL: (0, (1+n)·ℓ)<br/>AggXoL: (A, ℓ)"]:::Process
    G --> S["scale(f)<br/>QS: cession q<br/>CatXoL: —<br/>AggXoL: —"]:::Metric

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

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](https://reinsurance.dev/applications/standalone-pricing/#step-3-from-metrics-to-premium) prices C1 on:

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

```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](https://reinsurance.dev/glossary/#participation) is deliberately absent from the definitions: it is a [scaling](https://reinsurance.dev/financial-modelling/terms/scaling/) applied at [portfolio roll-up](https://reinsurance.dev/financial-modelling/portfolios/#the-roll-up-participation-then-sum), 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](https://reinsurance.dev/conventions/).

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

*[Table: Catalog table. See the web page: https://reinsurance.dev/financial-modelling/composition/]*

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):

### [Quota share](https://reinsurance.dev/financial-modelling/contracts/quota-share/)

The simplest structure: a fixed [cession percentage](https://reinsurance.dev/glossary/#cession-percentage) $q$ behind a filter and a contract period. Contract 5 cedes a quarter of Baltica's European windstorm losses:

$$
\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](https://reinsurance.dev/financial-modelling/contracts/quota-share/#with-event-limit) insert an occurrence excess and an aggregate excess before the scaling.

### [Catastrophe excess of loss](https://reinsurance.dev/financial-modelling/contracts/catxol/)

The occurrence excess clips each event; the aggregate excess caps the annual total (the limit $(1+n)\cdot\ell$ [encodes $n$ reinstatements](https://reinsurance.dev/financial-modelling/contracts/catxol/#reinstatements-as-the-aggregate-limit)). Contract 1 is \$30M excess of \$10M per Florida hurricane, with one reinstatement:

$$
\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})
$$

### [Aggregate excess of loss](https://reinsurance.dev/financial-modelling/contracts/aggxol/)

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](https://reinsurance.dev/financial-modelling/programs/#inuring) relationship, applied to the subject before the composition runs):

$$
\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](https://reinsurance.dev/financial-modelling/terms/) and [contract catalog](https://reinsurance.dev/financial-modelling/contracts/) formalise each block and each contract in turn. Beyond the canonical types, one contract can also forward its output to another. The [programs](https://reinsurance.dev/financial-modelling/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](https://reinsurance.dev/financial-modelling/perspectives/): subject, covered, gross, retained, and net.
