Code
import numpy as np
from aggregate import build, qd, __version__
from style import wprint, GT, refuse, figures, figure
figures()
print(__version__)1.43.1
Retrospectively rated premium for a large account, set by the account’s own loss between a minimum and a maximum
Stephen J. Mildenhall
2026-10-30
Retrospective rating sets an insured’s premium for a policy year after the year ends, from the account’s own losses. With account loss \(L\), a basic premium \(b\), a loss conversion factor \(m\), and a minimum \(\ell\) and maximum \(u\), the retrospective premium is \[ P(L) = \min\bigl(\max(b + mL,\ \ell),\ u\bigr). \] Retro plans are written for large commercial accounts, in workers compensation and general and auto liability, whose own experience is credible. The basic premium covers expenses and the charge for the collar. The loss conversion factor loads the losses for claim adjustment. Between the minimum and the maximum the insured pays \(m\) for each unit of its own loss, so it keeps most of its risk and buys protection only above the maximum. Swing rating applies the same collared map to the ceded premium of a reinsurance cover; retro applies it to the gross premium of the account.
The clause retro basic <b> lcm <m> [min <lo>] [max <hi>] premium is the premium head of a pnl, in place of a fixed or inherited premium (see Using pnl and xpnl). The minimum defaults to the basic and the maximum to no cap. The account must have no reinsurance, so that the account loss is the gross loss. An xpnl is refused, since there is no cession to walk through. The other variable features apply to reinsurance: reinstatements, the sliding scale, the profit commission, and the loss corridor.
1.43.1
The example is a large casualty account with a guaranteed cost premium of 1,000 at a 65% expected loss ratio, losses limited to 250 per claim, lognormal severity with mean 25 and CV 3, gamma mixed claim counts with mixing CV 0.2, and fixed expenses of 200. The retro plan has basic premium 250, loss conversion factor 1.1, minimum 600, and maximum 1,500. The account is written once, with a placeholder for the premium head.
account = '''pnl {name}
{head}
less
agg Account
1000 premium at 0.65 lr
250 xs 0
sev lognorm 25 cv 3
mixed gamma 0.2
less
200 fixed expenses'''
r = build(account.format(
name='Retro', head='retro basic 250 lcm 1.1 min 600 max 1500 premium'))
qd(r)
loss = r.engine
print(f'loss mean {loss.est_m:.2f}, median {loss.q(0.5):.2f}')
print(f'premium at the minimum {loss.cdf(350 / 1.1):.4f}, '
f'at the maximum {loss.sf(1250 / 1.1):.4f}')PnL('Retro': role=sell, 1 consideration, 2 obligation -> 'margin')
EX SD CV Skew P01 Median P99
Side
Consideration 962.01 268.94 0.27956 0.44463 600 926.78 1500
Obligation -850 280.42 -0.32991 -0.73085 -1643.6 -815.25 -364
Margin 112.01 57.161 0.51033 -5.5019 -143.62 113.5 236
loss mean 650.00, median 615.25
premium at the minimum 0.1002, at the maximum 0.0577
The Consideration row is the retrospective premium, with mean 962.01 and standard deviation (SD) 268.94, against an account loss with mean 650 and SD 280.42 (the obligation adds the expense of 200 to the loss). The premium is at the minimum of 600 when the loss is below \(350/1.1 = 318.2\), in 10.0% of years, and at the maximum of 1,500 when the loss exceeds \(1{,}250/1.1 = 1{,}136.4\), in 5.8% of years. In between it moves with the loss: the median premium, 926.78, is \(250 + 1.1 \times 615.25\), where 615.25 is the median loss.
The guaranteed cost policy charges the fixed premium of 1,000, the premium of the account (inherit premium). The left panel shows the two premiums against the account loss, and the right panel the distribution of the insurer’s margin, premium less loss and expense, under each plan.
gc = build(account.format(name='GC', head='inherit premium'))
x = np.linspace(0, 1600, 801)
m = np.linspace(-700, 700, 1401)
fig, (ax0, ax1) = figure('full', cols=2)
ax0.plot(x, np.full_like(x, 1000), label='guaranteed cost')
ax0.plot(x, loss.variable_terms.phi(x), ls='--', label='retro')
ax1.plot(m, gc.cdf(m), label='guaranteed cost')
ax1.plot(m, r.cdf(m), ls='--', label='retro')
ax0.set(xlabel='account loss', ylabel='premium', title='Premium')
ax1.set(xlabel='insurer margin', ylabel='distribution', title='Margin')
ax0.legend()
for k, p in {'guaranteed cost': gc, 'retro': r}.items():
print(f'{k:16s} margin {p.est_m:7.2f} SD {p.est_sd:6.2f} '
f'P01 {p.q(0.01):8.2f}')guaranteed cost margin 150.00 SD 280.42 P01 -643.62
retro margin 112.01 SD 57.16 P01 -143.62

The collar shares the loss back with the insured. Between the two breakpoints the insurer’s margin is \(250 - 200 + 0.1L\), rising from 81.8 to 163.6, because each unit of loss returns 1.1 in premium. Below a loss of 318.2 the premium stays at 600 and the insurer keeps the savings, with margin \(400 - L\); above 1,136.4 the premium stays at 1,500 and the insurer bears the whole loss, with margin \(1{,}300 - L\). Retro gives up 37.99 of the insurer’s expected margin. In exchange the SD of the margin falls from 280.42 to 57.16 and the 1 in 100 margin improves from \(-643.62\) to \(-143.62\).
Retro is refused in an xpnl and on an account with reinsurance.
R1: 'xpnl' over a retro program is not supported -- retro has no cession to walk
through. Use 'pnl'.
R2: 'retro' with reinsurance (or another variable feature) is not yet supported;
retro currently requires a book with no inuring reinsurance (the 1-D
net-account-loss = gross-loss case).
---
title: Using retro rating
subtitle: "Retrospectively rated premium for a large account, set by the account's own loss between a minimum and a maximum"
categories: [variable feature, P&L, pricing]
date: last-modified
jupyter:
jupytext:
text_representation:
extension: .qmd
format_name: quarto
format_version: '1.0'
kernelspec:
display_name: Python 3 (ipykernel)
language: python
name: python3
---
**Retrospective rating** sets an insured's premium for a policy year after the year ends, from the account's own losses. With account loss $L$, a basic premium $b$, a loss conversion factor $m$, and a minimum $\ell$ and maximum $u$, the retrospective premium is
$$
P(L) = \min\bigl(\max(b + mL,\ \ell),\ u\bigr).
$$
Retro plans are written for large commercial accounts, in workers compensation and general and auto liability, whose own experience is credible. The basic premium covers expenses and the charge for the collar. The loss conversion factor loads the losses for claim adjustment. Between the minimum and the maximum the insured pays $m$ for each unit of its own loss, so it keeps most of its risk and buys protection only above the maximum. [Swing rating](swing-rating.qmd) applies the same collared map to the ceded premium of a reinsurance cover; retro applies it to the gross premium of the account.
The clause `retro basic <b> lcm <m> [min <lo>] [max <hi>] premium` is the premium head of a `pnl`, in place of a fixed or inherited premium (see [Using pnl and xpnl](pnls.qmd)). The minimum defaults to the basic and the maximum to no cap. The account must have no reinsurance, so that the account loss is the gross loss. An `xpnl` is refused, since there is no cession to walk through. The other variable features apply to reinsurance: [reinstatements](reinstatements.qmd), the [sliding scale](sliding-scale.qmd), the [profit commission](profit-commission.qmd), and the [loss corridor](loss-corridor.qmd).
```decl
pnl NAME retro basic 250 lcm 1.1 min 600 max 1500 premium less <aggregate> [less <expenses>]
retro basic 250 lcm 1.1 max 1500 premium # minimum 250, the basic
```
```{python}
import numpy as np
from aggregate import build, qd, __version__
from style import wprint, GT, refuse, figures, figure
figures()
print(__version__)
```
## A retro-rated large account
The example is a large casualty account with a guaranteed cost premium of 1,000 at a 65% expected loss ratio, losses limited to 250 per claim, lognormal severity with mean 25 and CV 3, gamma mixed claim counts with mixing CV 0.2, and fixed expenses of 200. The retro plan has basic premium 250, loss conversion factor 1.1, minimum 600, and maximum 1,500. The account is written once, with a placeholder for the premium head.
```{python}
account = '''pnl {name}
{head}
less
agg Account
1000 premium at 0.65 lr
250 xs 0
sev lognorm 25 cv 3
mixed gamma 0.2
less
200 fixed expenses'''
r = build(account.format(
name='Retro', head='retro basic 250 lcm 1.1 min 600 max 1500 premium'))
qd(r)
loss = r.engine
print(f'loss mean {loss.est_m:.2f}, median {loss.q(0.5):.2f}')
print(f'premium at the minimum {loss.cdf(350 / 1.1):.4f}, '
f'at the maximum {loss.sf(1250 / 1.1):.4f}')
```
The `Consideration` row is the retrospective premium, with mean 962.01 and standard deviation (SD) 268.94, against an account loss with mean 650 and SD 280.42 (the obligation adds the expense of 200 to the loss). The premium is at the minimum of 600 when the loss is below $350/1.1 = 318.2$, in 10.0% of years, and at the maximum of 1,500 when the loss exceeds $1{,}250/1.1 = 1{,}136.4$, in 5.8% of years. In between it moves with the loss: the median premium, 926.78, is $250 + 1.1 \times 615.25$, where 615.25 is the median loss.
## Retro rating against guaranteed cost
The guaranteed cost policy charges the fixed premium of 1,000, the premium of the account (`inherit premium`). The left panel shows the two premiums against the account loss, and the right panel the distribution of the insurer's margin, premium less loss and expense, under each plan.
```{python}
gc = build(account.format(name='GC', head='inherit premium'))
x = np.linspace(0, 1600, 801)
m = np.linspace(-700, 700, 1401)
fig, (ax0, ax1) = figure('full', cols=2)
ax0.plot(x, np.full_like(x, 1000), label='guaranteed cost')
ax0.plot(x, loss.variable_terms.phi(x), ls='--', label='retro')
ax1.plot(m, gc.cdf(m), label='guaranteed cost')
ax1.plot(m, r.cdf(m), ls='--', label='retro')
ax0.set(xlabel='account loss', ylabel='premium', title='Premium')
ax1.set(xlabel='insurer margin', ylabel='distribution', title='Margin')
ax0.legend()
for k, p in {'guaranteed cost': gc, 'retro': r}.items():
print(f'{k:16s} margin {p.est_m:7.2f} SD {p.est_sd:6.2f} '
f'P01 {p.q(0.01):8.2f}')
```
The collar shares the loss back with the insured. Between the two breakpoints the insurer's margin is $250 - 200 + 0.1L$, rising from 81.8 to 163.6, because each unit of loss returns 1.1 in premium. Below a loss of 318.2 the premium stays at 600 and the insurer keeps the savings, with margin $400 - L$; above 1,136.4 the premium stays at 1,500 and the insurer bears the whole loss, with margin $1{,}300 - L$. Retro gives up 37.99 of the insurer's expected margin. In exchange the SD of the margin falls from 280.42 to 57.16 and the 1 in 100 margin improves from $-643.62$ to $-143.62$.
## Refused programs
Retro is refused in an `xpnl` and on an account with reinsurance.
```{python}
head = 'retro basic 250 lcm 1.1 min 600 max 1500 premium'
refuse(account.format(name='R1', head=head).replace('pnl', 'xpnl', 1),
account.format(name='R2', head=head).replace(
'mixed gamma 0.2', 'mixed gamma 0.2 aggregate net of 500 xs 1000'))
```