Skip to content
Property-cat reinsurance analytics for software engineers

Financial Modelling · Section 4.2

Contract period

The date-range term that admits only events inside a contract's inception-to-expiry window, handling mid-year inceptions, part-year covers, and runoff views identically.

Every reinsurance contract has an inception date and an expiry date that define the contract period. Losses from events outside this period are dropped before any transforming term sees them. In the year event loss table (YELT), every occurrence carries a timestamp; the contract period term is a date-range check that excludes rows whose timestamp falls outside inception-to-expiry:

period(t0,t1):keep row r of the YELT iff t0≤tr≤t1\text{period}(t_0, t_1): \quad \text{keep row } r \text{ of the YELT iff } t_0 \le t_r \le t_1

Both endpoints are inclusive: an occurrence on the inception date and one on the expiry date are both covered. The contract period is the loss filter specialised to the timestamp column: the same pointwise shape, and a predicate the YELT can always evaluate.

That check is a losses-occurring basis: an occurrence belongs to the contract when its event date falls inside the window, whichever policy it arose under. It is the basis this site assumes throughout, and the only one a YELT can express. Treaties are also written risks attaching (cover follows the policies incepting during the period, however late their losses fall)2 and on an underwriting year, which groups losses by the year the underlying policy was written; both need each loss’s original policy inception date, which no YELT row carries, so neither can be represented as an event-date filter. This page does not take them up.

Suppose this term covers 2024-04-01 to 2024-09-30, a mid-year inception. Applied to Trial 9, ten of the thirty occurrences fall outside the window and drop out: three early-year earthquakes and everything from 5 October on (seven occurrences, including the $80.8M Florida hurricane). At two decimals the two rows sum to the trial’s $272.12M subject, the figure the other term pages round to $272.1M:

Earthquake Hurricane

Contract period (Apr 1 to Sep 30) on Trial 9. The 10 dimmed occurrences fall outside the period; $175.6M of the trial's $272.1M is covered. The sliders set the first and the last month of the period, and the EP curve chart on this page follows them.

Contract periodOccurrencesSubject
In window (Apr 1 – Sep 30)20$175.57M
Outside window (dropped)10$96.55M
Trial 9 subject30$272.12M
caldera_re/contract_period.py Python
import pandas as pd

from caldera_re.data import cedents


def contract_period(
    loss: pd.DataFrame,
    period_start: str,
    period_end: str,
) -> pd.DataFrame:
    """Filter YELT to events within the contract period.

    Parameters
    ----------
    loss : pd.DataFrame
        YELT with a 'timestamp' column (string dates).
    period_start : str
        Contract inception date (inclusive), e.g. '2024-01-01'.
    period_end : str
        Contract expiry date (inclusive), e.g. '2024-12-31'.

    Returns a copy of the YELT containing only rows whose timestamp
    falls within [period_start, period_end].

    This is a losses-occurring basis (the occurrence's event date decides).
    Risks-attaching and underwriting-year bases need the original policy's
    inception date, which a YELT row does not carry.
    """
    ts = pd.to_datetime(loss['timestamp'])
    start = pd.Timestamp(period_start)
    end = pd.Timestamp(period_end)
    mask = (ts >= start) & (ts <= end)
    return loss.loc[mask].copy()


if __name__ == '__main__':
    # Full-year contract period: all events pass
    full_year = contract_period(cedents.suncoast, '2024-01-01', '2024-12-31')
    print("Full-year contract period (2024-01-01 to 2024-12-31)")
    print(f"  Rows in:  {len(cedents.suncoast)}")
    print(f"  Rows out: {len(full_year)}")
    print()

    # Partial-year: April to September (hurricane season focus)
    partial = contract_period(cedents.suncoast, '2024-04-01', '2024-09-30')
    print("Partial-year contract period (2024-04-01 to 2024-09-30)")
    print(f"  Rows in:  {len(cedents.suncoast)}")
    print(f"  Rows out: {len(partial)}")
    print()

    excluded = cedents.suncoast[
        (cedents.suncoast['trial_id'] == 9)
        & ~cedents.suncoast.index.isin(partial.index)
    ]
    occurrences = (excluded
                   .groupby(['timestamp', 'event_id', 'peril'], as_index=False)
                   .agg(geographies=('geography_id',
                                      lambda values: '/'.join(sorted(set(values)))),
                        loss=('loss', 'sum')))
    print("Trial 9 excluded occurrences (outside contract period):")
    for row in occurrences.itertuples(index=False):
        print(f"  {row.timestamp} | event {row.event_id} | "
              f"{row.peril} {row.geographies} | {row.loss:.2f}M")
    print(f"  {len(occurrences)} occurrences, "
          f"{occurrences['loss'].sum():.2f}M dropped")

The term is one boolean mask over the timestamp column: O(R)O(R) over the RR input rows, no sort and no grouping, and the output is never larger than the input. Placed early in a composition it reduces the rows every later term reads.

Across all 20 trials of the demo tier, the contract period reshapes the loss distribution. The covered curve is the in-window loss; the gap to the subject curve is what the period excludes (the amber curve), which the cedent retains:

Contract period (Apr 1 to Sep 30) over the 20 trials: SunCoast's subject against the covered loss. 20 of 20 trials hold a covered loss; the expected covered loss is $168.22M. The sliders are shared with the Trial 9 chart on this page.

For most Caldera Re examples, the contract period is the full calendar year 2024, so all events pass through. Step 2 of the dissection shows the no-op case. In practice, three cases produce partial-year windows: a mid-year inception, a cover written for less than twelve months, and a runoff view of a portfolio, which evaluates each contract over the part of its period still to run at the analysis date and assumes no renewal. The contract period term handles all three identically.

A window is a slice of the trial year, so a July-to-June inception (one of the common mid-year renewal dates) crosses into a year the trial does not contain; how a cat modeller bridges that gap, and how multi-year covers with anniversary resets extend the question, is out of scope for this page, as the conventions index records. The site’s examples keep their windows inside the trial year, as the April-to-September window above does.

A partial-year window is not a pro-rata share of the year’s risk: the half-year that holds hurricane season carries almost all of the basin’s storm activity, and the half-year that misses it carries almost none.3 The Trial 9 window above shows the effect on loss: the single $80.8M October hurricane is 84% of the $96.55M the window drops. Seasonality sets the split, not the day count.

The contract period is the second term of a catastrophe excess of loss (CatXoL) composition, between the loss filter and the occurrence excess.

  1. RAA, Fundamentals of Reinsurance and Glossary of Reinsurance Terms, on the distinction: “the lines of insurance in which liability is slowest to manifest itself or develop, the ‘long tail’ lines, create the worst problems for reinsurers”. ↩

  2. David R. Clark, “Basics of Reinsurance Pricing”, CAS study note, 2014. Contrasts a treaty on a “losses occurring” basis, priced on accident-year losses, with one on a “risks attaching” basis, “which covers losses on policies written during the treaty period”. ↩

  3. NOAA National Hurricane Center, “Tropical Cyclone Climatology”. The Atlantic hurricane season runs from 1 June to 30 November and peaks on 10 September, with most activity between mid-August and mid-October, so a half-year window that misses those months misses most of the basin’s storms. ↩