Swing-rated aggregate reinsurance, with a ceded premium set by the ceded loss between a minimum and a maximum

reinsurance
variable feature
P&L
Author

Stephen J. Mildenhall

Published

2026-11-01

A swing-rated cover sets its ceded premium after the treaty year from the reinsurer’s own losses. With ceded loss \(A\), a basic premium \(b\), a loss conversion multiplier \(m\), and a minimum \(\ell\) and maximum \(u\), the ceded premium is \[ p(A) = \min\bigl(\max(b + mA,\ \ell),\ u\bigr). \] Swing plans are used on working layers and aggregate covers whose losses are predictable enough that the cedant is content to pay them back. Between the minimum and the maximum, each unit of ceded loss raises the premium by \(m\), so the cover gives real protection only in the years when the premium reaches its maximum. The minimum defaults to the basic and the maximum to no cap.

The clause swing basic <b> lcm <m> [min <lo>] [max <hi>] takes the place of the deposit, rol, or rate premium clause on an aggregate layer. A pnl or xpnl books the result (see Using pnl and xpnl). A program has at most one variable feature: swing, a sliding scale, a profit commission, or a loss corridor. Swing on an occurrence layer is refused; there, reinstatements give the random ceded premium.

aggregate net of 4000 xs 8000 swing basic 200 lcm 1.1 min 500 max 2500
4000 xs 8000 swing basic 200 lcm 1.1 max 2500     # minimum 200, the basic
4000 xs 8000 swing basic 0 lcm 1.25 min 500        # no maximum
Code
import pandas as pd
from aggregate import build, qd, __version__
from style import wprint, GT, refuse, figures
figures()
print(__version__)
1.60.1

A swing-rated stop loss on the base book

The variable feature pages share one base book: premium 10,000 at a 70% loss ratio, a policy limit of 2,000, lognormal severity with mean 150 and CV 2.5, gamma mixed claim counts with mixing CV 0.25, and expenses of 25% of premium. The cover here is a stop loss of 4,000 xs 8,000, from an 80% to a 120% loss ratio, with swing premium \(200 + 1.1A\), at least 500 and at most 2,500.

Code
book = '''agg Book
  10000 premium at 0.7 lr
  2000 xs 0
  sev lognorm 150 cv 2.5 {occurrence}
  mixed gamma 0.25 {aggregate}'''
program = '''xpnl {name}
  inherit premium
  less
    ''' + book + '''
  less
    0.25 premium expenses'''
layer = 'aggregate net of 4000 xs 8000 {} as "Stop loss"'
s = build(program.format(name='Swing', occurrence='', aggregate=layer.format(
    'swing basic 200 lcm 1.1 min 500 max 2500')))
qd(s)
gross = s.engine
print(f'premium at the minimum {gross.cdf(8000 + 300 / 1.1):.4f}, '
      f'at the maximum {gross.sf(8000 + 2300 / 1.1):.4f}')
PnL('Swing': 2 groups, 5 legs -> 'margin')

                             EX     SD       CV     Skew    P01  Median   P99
Step      Side                                                               
Gross     Consideration   10000      0        0           10000   10000 10000
          Obligation      -9500   2704 -0.28463 -0.67687 -17071   -9193 -4633
          Margin            500   2704    5.408 -0.67687  -7071     807  5367
Stop loss Consideration -902.87  733.2 -0.81208  -1.4843  -2500    -500  -500
          Obligation      607.6 1160.2   1.9094   1.8962      0       0  4000
          Margin        -295.26 497.71  -1.6856   2.8642   -500    -500  1500
          Net            204.73 2352.5    11.49 -0.27905  -5571     307  4867
All       Consideration  9097.1  733.2 0.080597  -1.4843   7500    9500  9500
          Obligation    -8892.4 1841.3 -0.20706  0.20371 -13071   -9193 -4633
          Margin         204.73 2352.5    11.49 -0.27905  -5571     307  4867
          Impact        -295.26 497.71  -1.6856   2.8642   -500    -500  1500
premium at the minimum 0.7113, at the maximum 0.1297

The Stop loss step shows a ceded loss with mean 607.60, which is zero in more than half of all years, and a ceded premium with mean 902.87 and standard deviation (SD) 733.20. The premium is at the minimum when the ceded loss is at most \(300/1.1 = 272.7\), in 71.1% of years, and at the maximum when the ceded loss exceeds \(2{,}300/1.1 = 2{,}090.9\), in 13.0% of years. Without the collar the mean premium would be \(200 + 1.1 \times 607.60 = 868.36\); the minimum adds more than the maximum removes.

Swing rating against a fixed premium

A fixed premium of 902.87, the mean swing premium, on the 4,000 xs 8,000 stop loss gives the same mean results as the swing plan, so the comparison isolates the effect of the swing on the spread. The table shows the mean and SD of the ceded premium, the SD of the cover’s result (premium less recovery), and the mean, SD, and 1 in 100 result P01 of the cedant’s net result, with the uncovered book for reference.

Code
premium = -s.summary_df.loc[('Stop loss', 'Consideration'), 'EX']
f = build(program.format(name='Fixed', occurrence='',
                         aggregate=layer.format(f'deposit {premium:.2f}')))
rows = {}
for k, x in {'fixed': f, 'swing': s}.items():
    t = x.summary_df
    rows[k] = [-t.loc[('Stop loss', 'Consideration'), 'EX'],
               t.loc[('Stop loss', 'Consideration'), 'SD'],
               t.loc[('Stop loss', 'Margin'), 'SD'],
               *t.loc[('All', 'Margin'), ['EX', 'SD', 'P01']]]
g = s.summary_df.loc[('Gross', 'Margin'), ['EX', 'SD', 'P01']]
rows = {'no cover': [0, 0, 0, *g], **rows}
df = pd.DataFrame.from_dict(rows, orient='index', columns=[
    'premium', 'premium SD', 'cover SD', 'net', 'net SD', 'net P01'])
df.index.name = 'ceded premium'
GT(df, float_format=',.2f')
ceded premiumpremiumpremium SDcover SDnetnet SDnet P01
no cover0.000.000.00500.002,703.99-7,071.00
fixed902.870.001,160.18204.731,841.30-3,973.87
swing902.87733.20497.71204.732,352.46-5,571.00

Swing rating returns most of the volatility to the cedant. Inside the collar each unit of ceded loss raises the premium by 1.1, so the cedant pays back more than it recovers, and the cover gives real protection only above a ceded loss of 2,090.9, where the premium stops at its maximum. The SD of the cover’s result falls from 1,160.18 under the fixed premium to 497.71 under the swing, and the cedant’s net SD rises from 1,841.30 to 2,352.46, against 2,703.99 with no cover. The 1 in 100 net result is \(-5{,}571\) under the swing, against \(-3{,}974\) under the fixed premium and \(-7{,}071\) with no cover.

Refused programs

Swing is refused on an occurrence layer, alongside a deposit, rol, or rate clause, alongside cede on its own layer, and on a second layer, since a program has at most one variable feature.

Code
swing = 'swing basic 200 lcm 1.1 min 500 max 2500'
refuse(program.format(name='S1', aggregate='',
                      occurrence=f'occurrence net of 1000 xs 1000 {swing}'),
       program.format(name='S2', occurrence='',
                      aggregate=f'aggregate net of 4000 xs 8000 rol 0.2 {swing}'),
       program.format(name='S3', occurrence='',
                      aggregate=f'aggregate net of 4000 xs 8000 cede 0.1 {swing}'),
       program.format(name='S4', occurrence='',
                      aggregate=f'aggregate net of 4000 xs 8000 {swing} and '
                                f'4000 xs 12000 {swing}'))
DecL: variable-rating features (swing / slide / pc / corridor) decorate
aggregate reinsurance only. On an occurrence layer, reinstatements give a random
ceded premium.
DecL: 'swing' supplies the ceded premium and replaces the deposit / rol / rate
clause; give one or the other.
DecL: 'swing' supplies the ceded premium and the commission; give it without
'cede'.
DecL: at most one variable-rating feature per program; found 2.